Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
23ce6c1
Add crewmate delegation rule to brief scaffold
Jun 24, 2026
0dcc0f6
Add Frozen Gate Ledger groundwork for context watchdog
stoneevenson-biz Jun 26, 2026
479ea51
Add firstmate context watchdog (measure/watch/rehydrate + FIRSTMATE_R…
stoneevenson-biz Jun 26, 2026
418d5ea
Fix stale g1/g2 gate fixtures (add managed:true; g2 managed scratch s…
stoneevenson-biz Jun 26, 2026
e5a6b68
Add fm-compact-crewmate: on-demand single-crewmate compact (reuses da…
stoneevenson-biz Jun 26, 2026
3ca62a5
Add per-secondmate scoped context-watch, auto-started on secondmate boot
stoneevenson-biz Jun 26, 2026
861f8ae
docs: extend context-watchdog spec with on-demand compact and scoped …
stoneevenson-biz Jun 26, 2026
49d762f
chore(gates): refresh ledger last_verified after independent re-proof
stoneevenson-biz Jun 26, 2026
8ddd5ee
Fix stale-handoff immediate-clear and route crew sentinels to spawnin…
stoneevenson-biz Jun 26, 2026
33cba8a
Reconcile context-watch tests + brief gate-clause onto upstream reorg
stoneevenson-biz Jun 26, 2026
f029e80
docs(specs): agent-os council design — structural verifier stage (Pha…
stoneevenson-biz Jul 2, 2026
f73a45e
docs(plans): quarterdeck implementation plan (9 tasks, gates q1-q7)
stoneevenson-biz Jul 2, 2026
d3e3d6c
feat(quarterdeck): verdict grammar lib + gate-q1
stoneevenson-biz Jul 2, 2026
29a9862
fix(quarterdeck): make gate-q1 mutation bite (invalid-kind branch was…
stoneevenson-biz Jul 2, 2026
7707e13
fix(quarterdeck): fm_verdict_last returns 1 on decision-free verdict …
stoneevenson-biz Jul 2, 2026
f92fd30
feat(quarterdeck): merge hard gate + gate-q2
stoneevenson-biz Jul 2, 2026
b4a2480
feat(quarterdeck): pr-check hard gate + gate-q3
stoneevenson-biz Jul 2, 2026
642913a
feat(quarterdeck): fm-verify core - verifier seam, reject round-trip …
stoneevenson-biz Jul 2, 2026
c1d593d
feat(quarterdeck): attempt-cap gate-q5
stoneevenson-biz Jul 2, 2026
7cd2173
fix(quarterdeck): gate-q5 pins the third reject line, not just the es…
stoneevenson-biz Jul 2, 2026
4d8b1b6
feat(quarterdeck): lens-degrade gate-q6
stoneevenson-biz Jul 2, 2026
536be9a
feat(quarterdeck): fail-closed gate-q7
stoneevenson-biz Jul 2, 2026
952502e
feat(quarterdeck): brief verify clause + AGENTS.md operating docs
stoneevenson-biz Jul 2, 2026
ea5724c
fix(quarterdeck): disambiguate which done: triggers verification (no-…
stoneevenson-biz Jul 2, 2026
b99cc3c
chore(quarterdeck): ledger last_verified bookkeeping from verify runs
stoneevenson-biz Jul 2, 2026
5cce796
docs: wardroom (intake council) spec + implementation plan — agent-os…
stoneevenson-biz Jul 3, 2026
4a8d81b
refactor(wardroom): extract shared foreign-lens chain into fm-lens-li…
stoneevenson-biz Jul 3, 2026
e5d6959
feat(wardroom): intake channel grammar lib + gate-i1
stoneevenson-biz Jul 3, 2026
831263d
feat(wardroom): spawn hard gate + gate-i2
stoneevenson-biz Jul 3, 2026
9c7fb1e
feat(wardroom): fm-intake panel runner + gate-i3
stoneevenson-biz Jul 3, 2026
bab0082
feat(wardroom): revise-cap + fail-closed gate-i4
stoneevenson-biz Jul 3, 2026
5167a68
feat(wardroom): intake lens-degrade gate-i5
stoneevenson-biz Jul 3, 2026
9a94168
feat(wardroom): AGENTS.md intake council operating docs
stoneevenson-biz Jul 3, 2026
e465159
docs(wardroom): document the promotion-path intake boundary (review f…
stoneevenson-biz Jul 3, 2026
8f51c57
docs(specs): loop-conformance spec — agent-os phase 3
stoneevenson-biz Jul 3, 2026
96f211a
feat(loops): loop-engineering docs + loop-verifier role legibility
stoneevenson-biz Jul 3, 2026
e2c3e22
feat(loops): wake-drain observability + gates l1/l2 (loop-audit >= L2)
stoneevenson-biz Jul 3, 2026
48446af
docs(loops): supervision-protocol loop-observability note
stoneevenson-biz Jul 3, 2026
3c310c2
fix(loops): review findings - queue-root anchoring, secondmate skip, …
stoneevenson-biz Jul 3, 2026
7986c36
docs(specs): graph unification + routing manifest — agent-os phase 4
stoneevenson-biz Jul 4, 2026
24f6891
chore: loop-verifier.md -> symlink into the stone-skills Roster
stoneevenson-biz Aug 5, 2026
dc501d7
chore: capture working state before divergence reconciliation
stoneevenson-biz Aug 27, 2026
cbda336
fix(spawn): target tmux session explicitly with trailing colon
stoneevenson-biz Aug 27, 2026
e19d953
chore: loop-verifier link relative, matching the projection form
stoneevenson-biz Aug 27, 2026
cc58b74
chore: loop-verifier link back to absolute
stoneevenson-biz Aug 27, 2026
57bc27e
fix(spawn): prove the shell is ready before typing a launch command (#1)
stoneevenson-biz Aug 27, 2026
fa04182
feat: boot-time reconciliation digest in captain context (gate g-boot…
stoneevenson-biz Aug 27, 2026
00f0a37
feat(mux): the multiplexer seam - one contract, tmux and herdr driver…
stoneevenson-biz Aug 27, 2026
e6217b8
feat(herdr): one workspace per project, agent panes named for the wor…
stoneevenson-biz Aug 27, 2026
7120e54
feat(boot): read-only boot-context emitter and a status-reporting ver…
stoneevenson-biz Aug 29, 2026
38f08ae
feat(herdr): drive every crewmate pane through herdr, with no silent …
stoneevenson-biz Aug 29, 2026
5709948
feat(wardroom): give the intake council a reachable proceed with non-…
stoneevenson-biz Aug 30, 2026
6e6991a
fix(quarterdeck): the verifier honours gates/accepted-red.md (#8)
stoneevenson-biz Aug 31, 2026
899f1de
feat(wardroom): refuse an impossible brief before the spawn burns a r…
stoneevenson-biz Sep 1, 2026
93162ce
feat(supervision): follow crewmates onto herdr, and stop calling a do…
stoneevenson-biz Sep 2, 2026
6ddec94
fix(wardroom): stop the preflight gate asserting a property of the ma…
stoneevenson-biz Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ opencode, and pi).
## Busy-guard and composer guard

The daemon never injects into an in-use pane. Two checks run before every
injection (shared with `fm-send.sh` via `bin/fm-tmux-lib.sh`):
injection (shared with `fm-send.sh` via `bin/fm-tmux-lib.sh`; since the herdr cutover
`fm-send.sh` uses that path only for panes predating it, and reaches every new crewmate
through the acknowledged `herdr agent prompt --wait` in `bin/fm-herdr.sh`):

- **`pane_is_busy`** - the harness shows a busy footer (agent mid-turn).
- **`pane_input_pending`** - the cursor line holds real unsubmitted text (a
Expand Down Expand Up @@ -136,7 +138,9 @@ Classify each wake this way:
`FM_STALE_ESCALATE_SECS` (default 240s), housekeeping escalates it as a
possible wedge. This bounds wedge-detection latency to the threshold plus a
tick: a delay, never a loss. Healthy crewmates are autonomous and do not wait
on firstmate mid-task.
on firstmate mid-task. Since the herdr cutover the watcher emits no stale wake
for a herdr pane, so this path covers only panes still draining on tmux; the
gap and what covers it instead are in AGENTS.md, "herdr workspace hygiene".
- `heartbeat` -> self-handle. The daemon runs its own cheap bash fleet scan
every `FM_HEARTBEAT_SCAN_SECS` (default 300s) as the catch-all for a
captain-relevant status line the per-wake classifier might miss.
Expand Down
30 changes: 26 additions & 4 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The supervision knowledge lives here: busy signature, exit command, interrupt, d

Never dispatch a crewmate or secondmate on an unverified adapter.
If `config/crew-harness` names an unverified adapter, tell the captain and fall back to firstmate's own harness until that adapter is verified.
If the captain asks for a new harness, propose verifying it first: spawn a trivial supervised task using `fm-spawn`'s raw-launch-command escape hatch, confirm every fact empirically, then record the mechanics in `fm-spawn`, the busy signature in `fm-watch.sh` and `fm-tmux-lib.sh` defaults, any needed `FM_COMPOSER_IDLE_RE` empty-composer override, and the verified knowledge here.
If the captain asks for a new harness, propose verifying it first: spawn a trivial supervised task using `fm-spawn`'s raw-launch-command escape hatch, confirm every fact empirically, then record the mechanics in `fm-spawn`, the busy signature in `fm-watch.sh` and `fm-tmux-lib.sh` defaults (herdr reads agent state natively, so the signature only matters for panes predating the herdr cutover), any needed `FM_COMPOSER_IDLE_RE` empty-composer override, and the verified knowledge here.

## Detection

Expand All @@ -31,6 +31,27 @@ When verifying a new adapter, record its env marker and command name in `bin/fm-
For stuck recovery, the target window's harness is recorded as `harness=` in `state/<id>.meta`.
Use that value for interrupt, exit, resume, and skill-invocation facts.

## Relaunching or resuming an exited agent

Every resume and relaunch command below is a SHELL command, and an exited agent leaves a bare shell in its pane.
`bin/fm-send.sh` cannot deliver it: it refuses a pane with no detected agent, reporting that nothing was delivered and nothing was executed.
That refusal is deliberate rather than a defect.
The only way to get text into a shell pane is to run it, so forwarding a steer there would EXECUTE it, and an ordinary corrective line such as `git reset --hard origin/main` would run inside the crewmate's worktree.
Read the refusal as the guard working, and do not look for a way around it.

Use herdr's own verb, the same one `bin/fm-spawn.sh` uses to start an agent in a fresh pane:

```sh
herdr pane run <pane-id> '<resume or launch command>'
```

The pane id is the `window=` value in `state/<id>.meta` for a post-cutover pane, the ones marked `mux=herdr`.
Carry the same `FM_HOME=` / `HERDR_SESSION=` / `FM_*_OVERRIDE=` env prefix `fm-spawn` puts on its own launch string, or the relaunched agent loses the pins naming which firstmate home it belongs to.
For a pane predating the cutover, `window=` is a tmux session:window and `bin/fm-send.sh` types into that shell exactly as it always did.

There is no firstmate wrapper for this.
`fm_herdr_run` in `bin/fm-herdr.sh` is library-level only, and that script's CLI exposes only `--name` and the workspace reconcile, so the binary's verb is the supported route.

## no-mistakes skill invocation

Send the validation skill using the target harness's skill invocation form.
Expand Down Expand Up @@ -60,7 +81,7 @@ Firstmate launches every claude crewmate and secondmate with `CLAUDE_CODE_ENABLE
The CLI's `--prompt-suggestions` flag is print/SDK-mode only and does not suppress the interactive composer ghost text, verified empirically on v2.1.186.
As defense in depth for any pane that flag cannot reach, including the captain's own firstmate composer that away-mode reads, the pane reader in `bin/fm-tmux-lib.sh` captures only the composer line with ANSI styling, drops dim/faint SGR 2 runs, and ignores them, so only normal-intensity typed text counts as pending input.
That styled capture is internal to the boolean detector only.
`fm-peek` and every other human or LLM-facing capture path stays plain `tmux capture-pane` with no escape codes.
`fm-peek` and every other human or LLM-facing capture path stays a plain, escape-code-free read: `herdr agent read` (falling back to `herdr pane read`) for a post-cutover pane, `tmux capture-pane` for one still draining.

## codex (VERIFIED 2026-06-11, codex-cli 0.139.0)

Expand All @@ -77,6 +98,7 @@ The decision persists for the repo, so later worktrees of the same project skip

Resume after exit with `codex resume <session-id>`.
The session id is printed on quit.
Deliver it with `herdr pane run`, not `fm-send`; see "Relaunching or resuming an exited agent" above.

## opencode (VERIFIED 2026-06-11, v1.15.7-1.17.3)

Expand All @@ -88,8 +110,8 @@ The session id is printed on quit.

No trust dialog.
Opencode can auto-upgrade itself in the background and the running TUI can exit mid-task, observed live from 1.15.7 to 1.17.3.
If a pane shows the exit banner, relaunch with `--continue` to resume the session.
`--prompt` does not auto-submit alongside `--continue`, so send the next instruction via `fm-send` once the TUI is up.
If a pane shows the exit banner, relaunch with `--continue` to resume the session, delivering that command with `herdr pane run` rather than `fm-send`; see "Relaunching or resuming an exited agent" above.
`--prompt` does not auto-submit alongside `--continue`, so send the next instruction via `fm-send` once the TUI is up - by then the pane holds an agent again, which is what `fm-send` needs.

## pi (VERIFIED 2026-06-11)

Expand Down
8 changes: 6 additions & 2 deletions .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ bin/fm-backlog-handoff.sh <secondmate-id> <item-key>...
After seeding, run this handoff for the new secondmate's in-scope queued items.
The helper resolves the secondmate home from `data/secondmates.md` and mechanically moves each named item from the main `data/backlog.md` into the secondmate home's `data/backlog.md`.
It preserves the line and its section, so the item is neither duplicated nor lost.
It refuses `## In flight` entries because active task ownership also lives in tmux and `state/`.
It refuses `## In flight` entries because active task ownership also lives in the crewmate's live pane and `state/`.
It is idempotent; an item already in the secondmate backlog is skipped.
It refuses any destination that is not a genuine seeded firstmate home with safe operational directories and a matching `.fm-secondmate-home` marker, so a move can never land in a project.
Do not hand off `local-only` items.
Expand Down Expand Up @@ -106,7 +106,11 @@ Run `bin/fm-teardown.sh <id>` for `kind=secondmate` only when the captain or mai

The safety check is the secondmate's own home.
Teardown refuses while its `state/*.meta` contains in-flight work.
When safe, teardown kills the direct tmux window, removes the `data/secondmates.md` route, clears the main home metadata, and removes the retired secondmate home.
When safe, teardown closes the secondmate's pane through `fm_herdr_close_pane`, removes the `data/secondmates.md` route, clears the main home metadata, and removes the retired secondmate home.
That close routes to herdr for any post-cutover pane and falls back to `tmux kill-window` only for a window that predates the cutover, and a pane that is already gone counts as closed.
A close it genuinely could not perform is REPORTED - `warning: teardown could not close <pane> for <id>` - rather than swallowed, and the task record is KEPT at `state/<id>.orphan-pane` in the home that ran the teardown, holding the meta lines plus `orphan-pane=`, `orphan-mux=`, and `orphan-since=`.
Treat that warning as real: read the record, close the leftover pane it names, then delete the record, before considering the retirement finished.
The record lives outside the `state/*.meta` glob so a retired secondmate never reads as in flight, and a later teardown that does close the pane clears it, so its presence always means a pane is still open.
Removing a leased home releases its durable treehouse lease via `treehouse return`, so the pool slot is freed for reuse rather than left leased forever.
A plain-clone home with no pool slot is simply removed.
If `treehouse return` fails for a leased home, teardown stops with state intact rather than raw-removing the directory and hiding a held lease.
Expand Down
34 changes: 34 additions & 0 deletions .agents/skills/stuck-crewmate-recovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,38 @@ Escalate in order:
Genuine wedging means looping, unresponsive, repeating the same obstacle, or truly dead.
A low context reading is not wedging; modern harnesses auto-compact and keep going.
The worktree and commits persist, so relaunch is cheap.
Relaunch through the pane's shell, not through `bin/fm-send.sh` - see "Relaunching into a pane that holds no agent" below.
5. If a second relaunch fails too, write `failed` to the backlog and tell the captain with evidence.

## Relaunching into a pane that holds no agent

Once the agent has exited, the pane holds a bare shell, and `bin/fm-send.sh` will not touch it.
That refusal is deliberate, not a bug: the only way to put text into a shell pane is to run it, so forwarding a steer there would EXECUTE it, and a corrective line like `git reset --hard origin/main` would run in the crewmate's worktree.
`fm-send` therefore refuses a pane with no detected agent and reports that nothing was delivered and nothing was executed.
Do not look for a way around it.

Use herdr's own verb instead, which is exactly what `bin/fm-spawn.sh` uses to start an agent in a fresh pane:

```sh
herdr pane run <pane-id> '<launch command>'
```

The pane id is the `window=` value in `state/<id>.meta` (post-cutover panes, the ones marked `mux=herdr`).
Build the launch command from the adapter's entry in `harness-adapters`, and carry the same `FM_HOME=` / `HERDR_SESSION=` / `FM_*_OVERRIDE=` env prefix `fm-spawn` puts on its own launch string, or the relaunched agent loses the pins that tell it which firstmate home it belongs to.
For a pane that predates the cutover, `window=` is a tmux session:window and the old path still applies: `bin/fm-send.sh` types into that shell as it always did.

There is no firstmate wrapper for this today.
`fm_herdr_run` in `bin/fm-herdr.sh` is library-level only and `bin/fm-herdr.sh`'s CLI exposes just `--name` and the workspace reconcile, so the binary's verb is the supported route.

## Reading a steer's outcome

Delivery to a herdr pane is acknowledged rather than inferred, so `bin/fm-send.sh` names which failure you hit instead of leaving you to guess.

- **Refused at an approval dialog** - the crewmate is blocked and was not typed over.
Clear the dialog first with `bin/fm-send.sh <window> --key <choice>`, then send the corrective line.
- **Refused because the pane holds no agent** - nothing was delivered, and deliberately nothing was executed, since a pane holding a shell would have run the steer as a command.
Peek it: the agent has exited or is still starting, which is step 4 territory, not another steer.
If it has exited, relaunch with `herdr pane run` as described above; `fm-send` is never the tool for that.
- **Delivered but unconfirmed** - a warning, not an error.
Treat it as delivered and do not re-send it; re-sending a steer the crewmate already holds is the worse of the two errors.
- For a pane that predates the herdr cutover the old rule still holds: only a positively confirmed swallow, with the text left in the composer, is a failure.
1 change: 1 addition & 0 deletions .claude/agents/loop-verifier.md
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ jobs:
exit 1
}
tmux -V
- run: |
set -eu
for test_script in tests/*.test.sh; do
"$test_script"
done
# tests/run-all.sh, not a bare loop over tests/*.test.sh. A gate that is
# deliberately red - an accepted baseline, or one whose other half lives
# in another repo - would otherwise fail this job forever, and the wrong
# fix would be to make its test pass, which is the false green the gate
# ledger exists to prevent. The runner skips such a test only when the
# gate is BOTH red in gates/ledger.json AND declared in
# gates/accepted-red.md, announces every skip by name in the log, and
# skips nothing at all if either file is missing or unreadable.
- run: tests/run-all.sh

invariants:
name: Repo invariants
Expand Down
3 changes: 3 additions & 0 deletions .superpowers/sdd/progress-loopconf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Loop-conformance SDD progress
L1-L5 implemented inline by controller (docs, loop-verifier agent, wake-drain instrumentation, gates l1/l2, AGENTS.md note). Ledger green:19. Baseline audit was 29/L0; now 99 by tool (vacuous activity heuristics noted) - gate honestly pins >= L2 only.
Final: merged main 8f51c57..3c310c2 after re-review "Yes" (both Importants fixed red-first; fleet-churn closed via .fm-secondmate-home skip). Live ledger green:19. Accepted minors: items_found counts raw pre-dedup lines; Last-run stamp needs the anchor line present.
13 changes: 13 additions & 0 deletions .superpowers/sdd/progress-wardroom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Wardroom SDD progress
BASE: 5cce796
W1: complete (commits 5cce796..4a8d81b, review clean; Minor for final review: fm_lens_run degrade-write under set -eu could abort via $() subshell if review file unwritable - pre-existing)
W2: complete (commits 4a8d81b..e5d6959, review clean, zero findings; first attempt stalled with no side effects, retry succeeded)
W3: complete pending review (commits e5d6959..831263d; stalled implementer + dropped finisher, controller verified inline: i2 ok, mutation bites, spawn-batch/tangle/teardown/secondmate suites ok, ledger green:14; sweep fixed pre-existing P1 escape in fm-secondmate-safety fm-pr-check assertion)
W3: review approved (opus, live re-verification). Minors for final review: i2 case-5 env-prefix-on-function ordering is silently load-bearing; case-2 shared exit assertion message under mutation
W4: complete pending review (commit 9c7fb1e; implemented INLINE by controller after 3 subagent infra stalls; red observed 18:58:04 with test absent, green:15, mutation bites, /bin/bash -n clean)
W4: review approved (opus adversarial, byte-identical to brief, risks 1-7 clear). Minors: CRLF leak into evidence reasons; theoretical esac fall-through exits 0
W5: complete pending review (commit bab0082; inline; red 19:27:20, focused ok, mutation bites exit-3-got-2, green:16)
W6: complete pending review (commit 5167a68; inline; red 19:28:36, focused ok, mutation bites, green:17)
W7: complete pending review (commit 9a94168; inline; Wardroom section inserted between Intake and Spawn in section 7; 15 suites ok, green:17)
W5-W7: review approved (adversarial, verbatim-verified, mutations re-run). Finding handled: promotion path bypasses wardroom - documented as known boundary, structural fix queued
W8/final: complete (whole-branch review on opus: Ready to merge; ff-merged main 5cce796..e465159; live ledger green:17; follow-ups queued: promotion-path structural intake + promoted-recovery behavior, fm_lens_run degrade-write hardening, PANEL CRLF strip, intake-vs-Intake terminology note)
14 changes: 14 additions & 0 deletions .superpowers/sdd/progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Quarterdeck SDD progress
BASE: f73a45e
Task 1: complete (commits f73a45e..7707e13, review clean after 2 fixes: inert q1 mutation, fm_verdict_last no-decision contract)
Minors for final review: q1 test duplicate mutation comment; fm_verdict_append no newline-guard; plan doc still shows original inert mutation snippet
Task 2: complete (commits 7707e13..f92fd30, review clean)
Minor for final review: fm-teardown.test.sh does not actually exercise fm-merge-local paths (brief phrasing looseness)
Task 3: complete (commits f92fd30..b4a2480, review clean; assert_absent provenance confirmed by controller: tests/lib.sh:199)
Task 4: complete (commits b4a2480..642913a, review clean on opus; adjudicated: custom-lens fails loud-to-none by design; bash3.2 apostrophe rewording in PROMPT)
Minors for final review: fm-guard watcher-down stderr noise in q4 test output; exit 1 used for operational errors beyond usage
Task 5: complete (commits 642913a..7cd2173, review clean after 1 fix: missing reject-#3 assertion)
Task 6: complete (commits 7cd2173..4d8b1b6, review clean; recurring Minor: fm-guard TANGLE/WATCHER banners as noise in q-test captured output)
Task 7: complete (commits 4d8b1b6..536be9a, review clean, zero findings)
Task 8: complete (commits 536be9a..ea5724c, review clean after 1 fix: which-done disambiguation in AGENTS.md)
Task 9/final: complete (whole-branch review on opus: Ready to merge, 0 blockers; ff-merged to main at b99cc3c; follow-ups queued: fm_verdict_append newline-guard, exit-1 header wording; design decision surfaced: no-mistakes verifies pre-pipeline diff)
Loading