Skip to content

feat(bin): sync upstream firstmate through 8818a804 - #18

Merged
jbalke merged 24 commits into
mainfrom
fm/fm-reconcile-upstream-2026-10-23
Oct 23, 2026
Merged

jbalke merged 24 commits into
mainfrom
fm/fm-reconcile-upstream-2026-10-23

Conversation

@jbalke

@jbalke jbalke commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Intent

yes, sync upstream firstmate

Measured at intake, 2026-10-23: the upstream remote kunchenguid/firstmate has 22 non-merge commits this fork lacks, up to 8818a80, merge base 9fe25bc (the PR #17 sync, merged 2026-10-22); three-dot diff 108 files, 7,535 insertions, 857 deletions. Notable upstream themes: OpenCode 2 support (kunchenguid#7336); lane capacity facts with beat pressure (kunchenguid#6983); OpenRouter for typed dispatch resolution (kunchenguid#7149); notify owned PR conflicts and failing required checks behind an opt-in flag (kunchenguid#7634); read a PR with no CI checks as unverified, not green (kunchenguid#7627); Rex push notifications and grouping fixes (kunchenguid#7639, kunchenguid#7656); tmux container resolved from firstmate's own session (kunchenguid#7662); resurface queued wakes from handling successors (kunchenguid#7466); restore watcher downtime before main-only hand-back (kunchenguid#6588); away-mode inject deferral reasons (kunchenguid#6163).

What Changed

Risk Assessment

⚠️ Medium: The fork-specific work is small and correct: the one textual conflict (the bin/fm-contributions.sh header comment) is resolved cleanly, and enroll now calls get_input before write_record the same way retire and verdict already do, so the merge introduces no flat data/ paths in bin. It is still a 108-file upstream sync that brings substantial new runtime behaviour (OpenCode 2, lane capacity, PR health, Rex), and that behaviour was only spot-checked here against the fork's task-data layout.

Testing

I ran the real fm-contributions.sh enroll and owned commands in disposable lab homes, once with the pre-fix script and once with HEAD. Before the fix, enroll fails with exit 2 on a missing input.json and nothing is owned. At HEAD, enroll exits 0, writes data/tasks/front-client/ship1/contributions.json, owned lists the PR, and enrolling again is a no-op. I also booted a real Claude primary in a lab home on a private fm-lab tmux socket; its session-start digest ran to EXIT=0 with bootstrap silent (no missing or outdated tools). The Claude SessionStart hook itself stands down by design inside a gate worktree, so I drove the digest explicitly. The targeted suites all passed: PR health, contributions, contribution input, capacity facts for kunchenguid#6983, OpenRouter dispatch resolution for kunchenguid#7149, and session-start. Those suites use fixtures and fake forges, so the PR health monitor and the capacity and dispatch behaviour count as untested live. I removed the labs and the worktree is clean. There is no UI surface, so there are no screenshots.

  • Live validation: ✅ go - 4 of 6 scenarios driven live against the product
Scenario Result Live Evidence
A ship task with a project-tagged backlog row enrolls its PR. The record lands in data/tasks/<project>/<task>/contributions.json and owned reports the PR. ✅ pass live enroll-before-after.txt (HEAD section)
Adversarial: enroll with the merge-only script (pre-fix 87adf7b) fails and owns nothing, which proves the fork adaptation is required. ✅ pass live enroll-before-after.txt (PRE-FIX section: exit=2, owned [])
Re-enrolling the same task and PR is idempotent and exits 0. ✅ pass live enroll-before-after.txt (re-enroll exit=0)
The opt-in PR health monitor (kunchenguid#7634) wakes once on owned PR conflicts and failing required checks, keeps ownership through the fork-layout durable record after cleanup, and pauses once on an unreadabl… ⏸️ untested no The prior payload only established this through tests/fm-pr-health.test.sh against a fake gh forge, not a live result. Driving it live needs a real GitHub PR that can be mutated (conflicts, failing re…
A real Claude primary in a lab home runs the merged session-start digest to completion with no missing or outdated tools. ✅ pass live lab-claude-primary-session-start.txt (sections SESSION START..NEXT STEP, BOOTSTRAP silent, EXIT=0)
Lane capacity facts (kunchenguid#6983) and OpenRouter typed dispatch resolution (kunchenguid#7149) behave as specified. ⏸️ untested no The prior payload only established this through fixture-based tests (tests/fm-capacity.test.sh, tests/fm-dispatch-resolve.test.sh), not a live result. The OpenRouter path needs live provider credentia…
Evidence: enroll before/after fork fix (live CLI in lab homes)

Source: enroll before/after fork fix (live CLI in lab homes)

PRE-FIX: jq: error: Could not open file .../input.json ... exit=2; owned: []; no records HEAD: exit=0; owned: [{url:https://github.com/o/r/pull/8,tasks:[ship1],final:false}]; record at ./tasks/front-client/ship1/contributions.json; re-enroll exit=0

=== PRE-FIX (87adf7b0 merge only): fm-contributions.sh enroll ship1 https://github.com/o/r/pull/8
jq: error: Could not open file /var/folders/kc/90bft43x3sj0s76yp56ylh7c0000gn/T//fm-contributions.0Tt10i/input.json: No such file or directory
exit=2
--- owned:
[]
exit=0
--- records on disk:
--- re-enroll (idempotent):
jq: error: Could not open file /var/folders/kc/90bft43x3sj0s76yp56ylh7c0000gn/T//fm-contributions.9e0KCM/input.json: No such file or directory
exit=2

=== HEAD c7ed6619: fm-contributions.sh enroll ship1 https://github.com/o/r/pull/8
exit=0
--- owned:
[
  {
    "url": "https://github.com/o/r/pull/8",
    "tasks": [
      "ship1"
    ],
    "final": false
  }
]
exit=0
--- records on disk:
  ./tasks/front-client/ship1/contributions.json
{"task":"ship1","records":[{"url":"https://github.com/o/r/pull/8","kind":"pr"}]}
--- re-enroll (idempotent):
exit=0
Evidence: Lab Claude primary session-start transcript

Source: Lab Claude primary session-start transcript

  This script never starts supervision itself.
  The digest above is complete for this session start. The READ-ONCE CONTRACT
  section near the top of it governs what may still be read from disk.
  EXIT=0
  As you asked, I didn't act on any of it. I didn't wait for the network checks to finish, didn't create the missing records and didn't spawn anything. The run did claim the session lock for this
  temporary lab home and start the background network checks, because that's part of what the script does.
✻ Cooked for 41s · done 11:20 AM
❯ From the fm-session-start.sh output you already have (do not rerun anything), list every ===== section heading in order, and quote verbatim any line that reports an error, failure, missing or
  outdated tool, or warning. Keep it compact.
⏺ Captain, here are the ===== section headings in order:
  1. SESSION START - /var/folders/kc/90bft43x3sj0s76yp56ylh7c0000gn/T//fm-lab.C2b1Ku
  2. SUPERVISION OPERATING INSTRUCTIONS - primary harness: claude
  3. READ-ONCE CONTRACT
  4. FLEET STATE
  5. NETWORK CHECKS
  6. CONTEXT
  7. NEXT STEP
  The first section has four ----- subsections: LOCK, HOME ROLE, BOOTSTRAP and WAKE QUEUE.
  Errors, failures, or missing or outdated tools: none were reported. Bootstrap printed (silent - all good), and the script ended with EXIT=0. The supervision instructions do mention words like
  "FAILED", but only to describe what such notices would look like, not as actual results.
  Missing records: these are local data files, not tools. Each line reads ABSENT:
  - data/backlog.md
  - data/projects.md
  - data/secondmates.md
  - data/captain.md
  - data/captain-shared.md
  - data/learnings.md
  Warning-like lines: these come from the background network checks, which hadn't finished yet:
  - IN PROGRESS - the deferred network checks have not finished yet.
  - NOT yet confirmed: GitHub authentication, dead-secondmate relaunch, secondmate convergence, pending handoff delivery, project clone refresh with its drift reporting, and inactive terminal-outcome
    reconciliation.
✻ Baked for 8s · done 11:22 AM
Cache ██████████ 100% hit (71k cached) · warm 59:23 left [ Compact ]                                                                                                                                [-]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ Run bin/fm-startup-network.sh report and show its output
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   ~/./w/6/01M5M45SQTNKYZCM3DPQNGAE21  c7ed6619  ⊘  Opus 5.5  🌕 7%
  ⏵⏵ auto mode on (shift+tab to cycle) · ← 1 agent
Evidence: Targeted suite run log

Source: Targeted suite run log

FM_TEST_BEGIN 2026-10-23T10:17:59Z tests/fm-pr-health.test.sh family=pr-forge expected_gate_skip=none
ok - UNKNOWN then DIRTY wakes once
ok - DIRTY seen again at the same commits wakes nothing
ok - a conflict notifies once per latest commit despite recovery or base movement
ok - each required check that fails on an adopted draft wakes once per latest commit
ok - a fallback notification does not hide a later required failure at the same commit
ok - each check is judged by its newest run
ok - a latest commit that moves between the two reads waits for the next pass
ok - a red check on another home's PR wakes nothing in this home
ok - a PR enrolled before any ready report, and one kept after cleanup, both still wake
ok - an open PR that replaces a closed one on the same task branch is discovered
ok - a green PR without a reviewer never notifies
ok - a base with no required checks counts a failing reported check
ok - a retired contribution is not reclaimed through the cached branch lookup
ok - a retired contribution is not reclaimed by a fresh branch lookup
ok - an unreadable durable record pauses the monitor once and keeps its fingerprints
ok - nothing prints while nothing changed
ok - a merged PR leaves the monitor
ok - an absent flag leaves the home untouched
ok - the watcher turns one conflict into one durable wake
ok - check notifications survive cleanup and repeat only at a new latest commit
ok - all owned PR reads start in one wave without rotating a backlog
ok - all check pages contribute to required-check classification
ok - GitLab ownership and branch discovery make no GitHub reads
FM_TEST_END 2026-10-23T10:19:37Z tests/fm-pr-health.test.sh exit=0 duration_ms=98124 gate_skip=false
FM_TEST_BEGIN 2026-10-23T10:19:37Z tests/fm-session-start.test.sh family=session-bootstrap expected_gate_skip=none
ok - context digest distinguishes ABSENT, empty-but-present, and populated files
ok - a lock refusal prints a loud read-only banner, skips every mutating step, and still completes the digest
ok - session start stays read-only when lock ownership cannot be published
ok - locked session start freezes trace context and lock refusal leaves it unchanged
ok - concurrent session-lock acquisition admits exactly one live harness
ok - digest sections are ordered safety-preamble first, live fleet state before curated memory
ok - the read-once contract is stated once, ahead of the sources it governs
ok - session start: configured and auto-detected Herdr homes never require tmux
ok - session start: an absent recorded tmux window relaunches its Pi secondmate exactly once, off the blocking path
ok - session start: a deferred relaunch is always reported, so the digest's stale endpoint record cannot stand
ok - session start: inactive reconciliation runs after the digest and retains its durable wake
ok - session start: an unreachable host delays a reported check, not the digest
ok - session start: a deferred result the digest outran still reaches the agent as a wake
ok - session start: a read-only session declares its skipped network checks rather than dropping them
ok - session start: the tasks-axi compatibility verdict is computed once and reused
ok - session start: an existing ambiguous Pi process prevents duplicate recovery
ok - session start: transient tmux unreadability never licenses a relaunch
ok - session start: the proven bare-shell recovery path remains intact
ok - session start: a confirmed Herdr husk is closed and relaunched
ok - status tail is bounded to the configured line count, with the full log path always printed
ok - status tail lines are capped with a truncation marker while the full log stays reachable
ok - orphan status logs are printed once with bounded tails
ok - tmux endpoint liveness is reported per task: alive for a live window, dead for a gone one
ok - herdr endpoint liveness is reported per task: alive, dead for exit 1, dead for any other probe status
skip: /proc not readable (the read-death shape needs process ancestry)
Terminated: 15
ok - a hung per-task endpoint read hits its configured bound, reports the task, and leaves nothing stuck
Terminated: 15
ok - a padded-zero per-read bound falls back to the 10s default instead of removing the bound
ok - the perl timeout fallback reports a signal death as a nonzero status
skip: /proc not readable (the digest-death shape needs process ancestry)
ok - fm-session-start.sh composes the real fm-lock.sh, fm-bootstrap.sh, and fm-wake-drain.sh output verbatim
ok - locked Pi session start replays leading routine outcomes, preserves the captain barrier, and sweeps only dead leases
ok - non-Pi session start neither sweeps nor replays Pi branch state
ok - session start seeds an existing outcome store's absent display tail copy while away, moving no marker
ok - compatible tasks-axi backlog rendering drops done rows and keeps every in-flight, held, and blocked row
ok - the startup backlog bound cuts only dispatchable queued rows and discloses the remainder exactly
ok - manual backlog rendering drops done rows, keeps every held or blocked title line, and bounds the rest
ok - unavailable or incompatible tasks-axi falls back to compact manual backlog rendering
ok - an empty fleet reports (none) for in-flight tasks and an absent AFK flag
ok - session start emits X-mode cadence guidance in the harness supervision block
ok - next step delegates watcher ownership to the AFK daemon
ok - next step delegates watcher ownership to the daemon in quiet mode, distinctly from away mode
ok - the AFK digest reads a quiet record as a present captain holding nothing, and an away record as hold-for-return
ok - a legacy empty .afk flag (written before mode existed) still reads as away mode
ok - session start emits exactly one detected harness block and reports Pi extension load state
ok - session start preserves pi-signed primary identity while applying Pi extension guarantees
ok - session start rejects stale Pi loaded markers
ok - session start rejects a Pi watcher generation left in handoff
ok - session start accepts current Pi markers written before lock acquisition
ok - session start emits the omp block and reports omp extension load state
ok - omp diagnostic keeps today's wording for the checkout home and names the establishable cause
ok - session start accepts current omp markers written before lock acquisition
ok - session start rejects Pi sessions missing the turn-end guard marker
ok - session start rejects Pi loaded markers from previous sessions
ok - the pure-Bash watchdog bounds session start, kills its hung grandchild, and emits the truncation contract
ok - the portable timeout path force-kills a command that ignores TERM
ok - a session start inside its budget prints no truncation banner
ok - the runtime bound leaves enough ancestry headroom for a deeply nested session to take the lock
ok - --reemit reprints the digest without repeating startup's mutating sweeps and still drains queued wakes
ok - --reemit puts its read cue in the first 2000 characters and a normal start omits it
ok - true-start AGENTS baselines stay immutable while every drifted Pi compact re-emits the current contract
ok - read-only Pi compact refreshes against the rebuilding session identity without mutation
ok - Codex reset sources do not claim an unavailable instruction-refresh channel
ok - instruction baselines require SHA-256 and successful startup completion
ok - --reemit re-verifies lock ownership and keeps repair ownership with whoever holds it
# fm-session-start.test.sh: all assertions passed
FM_TEST_END 2026-10-23T10:29:39Z tests/fm-session-start.test.sh exit=0 duration_ms=601277 gate_skip=false
FM_TEST_BEGIN 2026-10-23T10:29:39Z tests/fm-capacity.test.sh family=standalone expected_gate_skip=none
FM_TEST_BEGIN 2026-10-23T10:29:39Z tests/fm-dispatch-resolve.test.sh family=standalone expected_gate_skip=none
ok - absent key is off: one stderr line, exit 0, no network call
ok - TYPESAFE_API_KEY= in .env activates the tool; environment and config overrides work
ok - clear: one rule Choice request, key on the fd header only, spendPriority argmax over every candidate
ok - OpenRouter clear uses the same output contract and keeps its key ou

... [2249 bytes truncated] ...


ok - keyed config lines parse, and unknown, repeated, or malformed lines are refused
ok - a fresh beat replaces probes, and its warn level closes lanes with its reason
ok - late beats count, and stale, missing, malformed, or foreign beats fall back to probes
ok - several pressure files: worst level wins, first supplies memory, every disk counts
ok - a beat with missing fields keeps its fields aligned and limits fall back to probes
ok - the lane footprint memory rule applies only with min-avail-gb
ok - limits close lanes with named reasons, and --admit exits 0, 75, or 1
ok - publish is opt-in, private, throttled, records boots, and publishes an invalid config as invalid
ok - captain presence and quota runway publish derived words only, unknown when unreadable
ok - the watcher publishes lane facts only for an opted-in home
ok - real host probes produce a well-formed report
# all fm-capacity tests passed
FM_TEST_END 2026-10-23T10:30:06Z tests/fm-capacity.test.sh exit=0 duration_ms=27621 gate_skip=false
FM_TEST_BEGIN 2026-10-23T10:30:06Z tests/fm-contributions.test.sh family=snapshot-bearings expected_gate_skip=optional-binary
ok - grouped contribution reads and writes take precedence and legacy remains readable
ok - grouped contribution paths refuse symlinks at every new level
ok - an unusable project hint groups under no-project instead of ending the poll
ok - a task id the data layer cannot name owns nothing and costs no forge read
ok - an unusable task id raises no captain wake and does not end the cycle
ok - a trailing slash on the data root writes records and still refuses a symlinked root
ok - only required-captain contributions are rows; other actors are counted
ok - replaced-head verdict is stale and cannot create a captain requirement
ok - unchecked ownership is disclosed and cannot prove silence
ok - newest check with no verdict is distinct from passing and pending
ok - new maintainer comment wakes once and stays pending until acknowledged
ok - new maintainer review wakes once and stays pending until acknowledged
ok - new maintainer inline wakes once and stays pending until acknowledged
ok - ready-for-pr on a filed issue becomes a planning wake
ok - a fresh open issue remains measured maintainer triage
ok - an absent check lane remains missing across repeated observations
ok - mixed freshness retains measured captain work and discloses the gap
ok - malformed durable evidence cannot prove silence
ok - a transient ready-for-pr label wakes and its exact acknowledgement survives replay
ok - recorded judgment keeps its exact head and is stale immediately on a published replacement
ok - verdict help and refusal name exactly the actors the command accepts
ok - a current forge observation refreshes a verdict after a replacement
ok - an unavailable current head leaves verdict freshness unknown
ok - away yolo delivery is fleet work without granting merge authority
actionable: PR https://github.com/o/r/pull/8 is registered but its ready line did not reach the parent channel (rc=3)
ok - cross-home away yolo delivery is fleet work
ok - retired ownership persists and unsupported forge remains visibly unmeasured
ok - unsupported forge coverage is disclosed without inventing fleet work
ok - held unsupported forge coverage remains unmeasured
ok - unsupported ownership stays silent and unmeasured; legacy errors clear without fabricated evidence
ok - legacy cleanup preserves unsupported ownership and supported missing-evidence failure in the same file
ok - shared contribution signal wakes once while retaining both acknowledgements
ok - watcher keeps observer diagnostics separate from contribution wakes
ok - expired child unsupported-forge coverage remains unmeasured
ok - watcher surfaces one newly durable contribution signal without re-ringing it
ok - parent consumes measured child coverage and refuses expired child silence
ok - unreadable pending signals refuse an empty-inbox claim
ok - record task identity is the directory dirname/basename named
ok - snapshot and pending create nothing in a home without state
ok - budget exhausted mid-observation (exhaust) keeps the prior record and stays silent
ok - budget exhausted mid-observation (hang) keeps the prior record and stays silent
ok - a genuine forge failure inside the budget still records the error, its cause and wakes
ok - one HTTP 500 is retried once and a successful retry stays healthy
ok - two transient failures wake once and name the failed read in the record
ok - an HTTP 404 is reported without retrying
ok - real gh error shapes drive the intended retry decision and keep their error
ok - a URL never inherits the previous URL read diagnostic
jq: error (at /var/folders/kc/90bft43x3sj0s76yp56ylh7c0000gn/T//fm-contributions.xsVSA7/after.json:1): null (null) cannot be matched, as it is not a string
ok - a URL owned by two tasks is observed once and every owner receives the result
ok - a merged or closed contribution settles once, is not re-read, and never wakes again
ok - a late owner inherits a terminal observation without a forge read or wake
ok - a retry converges every owner whose saved row is not terminal, keeping its own acknowledgement state
ok - an open PR linked from a done task keeps being observed
ok - a later URL waits when fewer than fifteen seconds remain for its observation
ok - eight 3-second PR reads complete fresh within one 20-second poll cycle
ok - a read killed at the five-second bound is budget refusal and stays silent
ok - rotation preserves timed-out records and refreshes every slow PR on successive cycles
ok - the effective budget is cut down to the watcher per-check bound with margin
ok - generated checks enforce configured and inherited budgets at runtime
ok - a genuinely unavailable forge records an error and wakes once per failure episode
ok - a late owner does not restart a shared forge failure episode
ok - retire stops the unavailable check, leaves rotation and restores complete coverage
ok - a late owner settled beside a retired final record stays unretired and known
ok - retire is idempotent and refuses non-captain, unknown, malformed and signal-bearing pairs
ok - an acknowledged unanswered thread under a hold is raised again after a day, daily, until the author replies
ok - a resolved thread with no reply counts as answered
ok - only inline threads and reviews requesting changes with text, no author reply, resolution or later verdict are tracked as unanswered
ok - the reply age is a validated seconds setting
ok - a closed pull request stops raising its threads
ok - the unanswered re-raise and the review-thread read stay off unless a reply age is configured
ok - arm copies a configured reply age into the generated check
ok - a many-review PR stores only classified review fields and stays readable
FM_TEST_END 2026-10-23T10:36:33Z tests/fm-contributions.test.sh exit=0 duration_ms=386677 gate_skip=false
FM_TEST_BEGIN 2026-10-23T10:36:33Z tests/fm-fleet-snapshot-contribution-input.test.sh family=unclassified expected_gate_skip=none
ok - contribution-input propagates a failing jq assembly as a non-zero exit
FM_TEST_END 2026-10-23T10:36:34Z tests/fm-fleet-snapshot-contribution-input.test.sh exit=0 duration_ms=843 gate_skip=false
FM_TEST_SUMMARY total=6 failed=0 skipped_gate=0 duration_ms=1115470
FM_TEST_SUMMARY_FAMILY family=pr-forge count=1 duration_ms=98124 failed=0
FM_TEST_SUMMARY_FAMILY family=session-bootstrap count=1 duration_ms=601277 failed=0
FM_TEST_SUMMARY_FAMILY family=snapshot-bearings count=1 duration_ms=386677 failed=0
FM_TEST_SUMMARY_FAMILY family=standalone count=2 duration_ms=54041 failed=0
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=843 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-session-start.test.sh duration_ms=601277
FM_TEST_SLOWEST rank=2 script=tests/fm-contributions.test.sh duration_ms=386677
FM_TEST_SLOWEST rank=3 script=tests/fm-pr-health.test.sh duration_ms=98124
FM_TEST_SLOWEST rank=4 script=tests/fm-capacity.test.sh duration_ms=27621
FM_TEST_SLOWEST rank=5 script=tests/fm-dispatch-resolve.test.sh duration_ms=26420
FM_TEST_SLOWEST rank=6 script=tests/fm-fleet-snapshot-contribution-input.test.sh duration_ms=843
exit=0

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - medium risk

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 6 scenarios driven live against the product
Scenario Result Live Evidence
A ship task with a project-tagged backlog row enrolls its PR. The record lands in data/tasks/<project>/<task>/contributions.json and owned reports the PR. ✅ pass live enroll-before-after.txt (HEAD section)
Adversarial: enroll with the merge-only script (pre-fix 87adf7b) fails and owns nothing, which proves the fork adaptation is required. ✅ pass live enroll-before-after.txt (PRE-FIX section: exit=2, owned [])
Re-enrolling the same task and PR is idempotent and exits 0. ✅ pass live enroll-before-after.txt (re-enroll exit=0)
The opt-in PR health monitor (kunchenguid#7634) wakes once on owned PR conflicts and failing required checks, keeps ownership through the fork-layout durable record after cleanup, and pauses once on an unreadabl… ⏸️ untested no The prior payload only established this through tests/fm-pr-health.test.sh against a fake gh forge, not a live result. Driving it live needs a real GitHub PR that can be mutated (conflicts, failing re…
A real Claude primary in a lab home runs the merged session-start digest to completion with no missing or outdated tools. ✅ pass live lab-claude-primary-session-start.txt (sections SESSION START..NEXT STEP, BOOTSTRAP silent, EXIT=0)
Lane capacity facts (kunchenguid#6983) and OpenRouter typed dispatch resolution (kunchenguid#7149) behave as specified. ⏸️ untested no The prior payload only established this through fixture-based tests (tests/fm-capacity.test.sh, tests/fm-dispatch-resolve.test.sh), not a live result. The OpenRouter path needs live provider credentia…
  • Live lab home (bin/fm-lab-home.sh create), compared before and after the fix: fm-contributions.sh enroll ship1 https://github.com/o/r/pull/8, then owned, then enroll again, run with the 87adf7b0 copy of bin/fm-contributions.sh and then with HEAD c7ed6619. The backlog row carries (repo: front-client).
  • Live lab Claude primary started with env -u ... TMUX_TMPDIR=$LAB/tmux tmux -L fm-lab new-session -d -s primary -e FM_HOME=$LAB claude. It was driven with send-keys to run bin/fm-session-start.sh, captured with capture-pane, and stopped with kill-server before rm -rf $LAB.
  • Traced the Claude SessionStart hook directly with bash -x bin/fm-sessionstart-run.sh in the lab env.
  • bin/fm-test-run.sh tests/fm-pr-health.test.sh tests/fm-contributions.test.sh tests/fm-fleet-snapshot-contribution-input.test.sh tests/fm-capacity.test.sh tests/fm-dispatch-resolve.test.sh tests/fm-session-start.test.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

LorenzoMinghini and others added 24 commits October 22, 2026 07:22
…ollback (kunchenguid#7625)

* no-mistakes(review): wedge cap: guard the cap-fire counter reset with rollback

* no-mistakes(document): docs: repair shard-pack wording and wedge-cap marker shorthand

* no-mistakes(review): docs: correct away-release terminal-row bound claim

* no-mistakes(document): docs: cover third guarded cap-state write in watcher comment

* no-mistakes(ci): Reweighted the packing hint for tests/fm-supervision-host.test.sh from the local 789123 ms to 1192918 ms, the one completed successful CI sample (run 39642852135, job 119276758966), and let the LPT packer rebalance. The guard was not weakened (PORTABLE_SERIAL_MAX_WEIGHT_MS=1200000, PORTABLE_SERIAL_SHARDS=9, coverage guard and packing-boundary check untouched) and nothing was hand-placed. New packed hint sums of the 9 serial shards (ms): 1=1192918 (1 script), 2=1139923 (18), 3=1139903 (30), 4=1139922 (30), 5=1139917 (30), 6=1139886 (29), 7=1139891 (30), 8=1139880 (29), 9=1139911 (31). Total 228 scripts = the serial lane exactly; max 1192918 < 1200000, so it fits. Packing guard: bin/fm-test-run.sh --check-coverage -> FM_TEST_COVERAGE ok total=268 parallel=24 parallel_max_ms=665545 parallel_imbalance_ms=2 parallel_unhinted=0 serial=228 serial_shards=9 serial_unhinted=15 serial_max_ms=1192918 serial_budget_ms=1200000 herdr=16, exit 0. tests/fm-test-run.test.sh passes both packing contracts: 'coverage guard bounds the unmeasured share and serial packing within twenty minutes' and 'serial packing accepts the exact budget and refuses one millisecond above it'. bin/fm-doc-audience-check.sh ok (surfaces=124 local_links=851). docs/fm-test-portable-shards.md now states the same 1192918 ms for that script, citing run 39642852135 (documented duration == packed weight), and adds the required honesty note in the same paragraph style: run 39765114423 cancelled this script alone in a shard at the 30-minute job cap and upstream run 39618934309 hangs identically, so the weight is a fit to the 20-minute packing target, not a cure, and the underlying slowness is an upstream defect this branch does not own. No other file, weight, test or doc line touched. Unrelated pre-existing failure, verified by git stash at base commit 5af4072: tests/fm-session-lock-ancestry.test.sh asserts 'the pty-host was not reparented to init after the daemon ended' and fails identically on the unmodified tree (sandbox pty behaviour); the --jobs family-admission assertion in the same suite fails only because of it. Not caused by this change
…nguid#6588)

* fix(bin): restore inherited handling before main-only hand-back

* Restored downtime so an earlier engine turn could not suppress a later
  main-owned close while leaving a healthy successor behind
* Routed restoration failures through the existing Stop-hook alarm
  instead of silently discarding the hand-back
* Added host-and-hook regressions plus attended and away idle-return
  controls to separate missing monitoring from status classification

* no-mistakes(test): Assert ordered stale event in returned-driver controls

* no-mistakes(document): Document main-only hand-back downtime restoration and failure

* no-mistakes(document): Correct host header on main-only downtime republish
…unchenguid#7639)

* feat(rex): push blocked edges from Rex program status through a bounded Lua reader

Rex joins Herdr as a push-capable backend: a bounded rex do reader streams
program_status_changed, child_exited, and block_closed for the watched task
blocks, and a trusted blocked edge wakes the supervisor with Rex's blocked
kind and message in the reason. The per-pane dedupe moves into the shared
transition lib, the record gains a detail field, and gone edges end the wait
for an immediate poll. The real-Rex test isolation is shared by both smokes.

* fix(rex): anchor the composer read on Rex's cursor position

Rex's format method reports the cursor alongside the styled grid, so the
composer verdict now uses the same cursor-anchored read as tmux and falls
back to the cursorless read only where that is unknown. OpenCode 1.19 draws
a status line below its composer, which made every cursorless read of an
idle OpenCode worker on Rex unknown and refused steers, exit, and relaunch.

* test(rex): add a live composer guard for every installed harness on Rex

* docs(rex): record push-event and per-tool live evidence on Rex

* no-mistakes(document): Clarify Rex documentation and shared transition ownership

* no-mistakes(ci): Fixed flaky public-followup fixture teardown: killing only the remote worker child allowed its supervisor to restart and race directory removal. Cleanup now uses the existing whole-worker-tree stop helper. Verified the full public-followup suite, remote-job orphan-reap suite, shell syntax, and git diff checks all pass
…nd an opt-in flag (kunchenguid#7634)

* refactor(bin): share the branch-to-PR lookup through fm-pr-lib

* feat(bin): notify owned PR health changes behind an opt-in flag

* no-mistakes(review): drop status-line ownership, key checks per name, remove PR wait rule

* no-mistakes(review): rediscover the branch PR once the cached one is final

* no-mistakes(review): refuse retired re-enrollment, require marked checks, verify owned records

* no-mistakes(document): note shared branch-to-PR lookup in scripts table

* fix(bin): notify reported failures when no checks are required

* no-mistakes(document): inventory PR health flag and state, note new pr-forge member
* Isolate real Rex tests with private servers

* no-mistakes(review): Trap Rex cleanup early and prove passive-read autostart guard

* no-mistakes(document): Consolidate Rex test isolation documentation
* fix(bin): report an outdated bootstrap tool instead of a missing one

detect_local_tools printed "MISSING:" for a treehouse without --lease, a
no-mistakes below 1.46.0, a gh-axi below its floor, and an incompatible
tasks-axi or quota-axi, so a supervisor read an installed tool as absent
and took the first-install remediation again.

Report those as "OUTDATED:" with the installed version, the requirement
and the tool's own upgrade command: treehouse update, no-mistakes update,
gh-axi update and quota-axi update. tasks-axi ships no self-updater, so it
keeps its install command. "MISSING:" stays for absent tools only, and
lavish-axi keeps PRESENTATION_UNAVAILABLE with its text fallback.

Update the bootstrap-diagnostics handler, the AGENTS.md section 13 trigger
list and the operator diagnostics in docs/configuration.md, and cover
absent, below-floor and at-floor for every floored tool in
tests/fm-bootstrap.test.sh.

* no-mistakes(document): Documented distinct bootstrap tool diagnostics

* fix(bin): reuse the tasks-axi check's own probe results

Rendering the tasks-axi OUTDATED requirement text ran the two `--help`
capability probes again after the compatibility check had already run
them, including when session start handed bootstrap an incompatible
verdict. The check now records the reason behind its verdict - the
version floor, or the capability probes the build fails - and reuses it,
both within the process and across the existing one-hop handoff, so
naming a missing capability costs no second probe pass. A build below the
floor is answered by its version alone and never pays for capability
probes it cannot use.

The bootstrap tests that simulate a tool as absent removed it from the
fake bin directory while bootstrap kept searching the fallback base PATH,
so a real host install could satisfy the lookup instead of the expected
MISSING line. Those cases now hide the tool from the base path as well,
and a new case pins the contrast between a bare and a curated fallback.

Verified: the touched bootstrap suite passes, and its probe-count and
capability assertions fail against the pre-fix scripts. Run with a
fallback base PATH carrying real installs of the absent tools, the
pre-fix suite fails on "absent treehouse reports a missing tool" while
the fixed suite passes every case.

* no-mistakes(document): Documented missing versus outdated tool diagnostics

* no-mistakes(document): Refresh outdated-tool documentation

* no-mistakes(review): Reuse shared version parser for outdated diagnostics

* no-mistakes(document): Correct outdated-tool diagnostics documentation

* no-mistakes(document): Align toolchain upgrade documentation

* fix(skills): keep main's bootstrap-diagnostics description and only add OUTDATED
…e verbatim match (kunchenguid#7530)

* Clarify ahoy legacy startup exclusion

* Fix quoted payload code span
…unchenguid#7637)

* fix(timeout): bypass shell builtins when invoking external runners

* no-mistakes(review): Remove redundant slashless runner resolution from external timeout
…urable (kunchenguid#6094)

* fix: count answered captain holds in the Done archive at the completion gate

Done retention moves closed markdown rows into the configured Done archive,
which tasks-axi show never reads, so verify, complete, and scout teardown
refused an answered captain hold once retention archived it. The completion
gate now reads the newest archived block for an attested entry, under its
exact or legacy identity, and accepts it only through a recorded resolution.
Unanswered archived closes and holds found nowhere still refuse.

* no-mistakes(document): document Done-archive durability in captain-hold verification record

* no-mistakes(ci): Fixed the selected Greptile findings in bin/fm-captain-hold.sh (all in the Done-archive read path added by this PR). code_change_needed=true. ci-3 (Archived reads can hang): task_show_archived() now bounds the replayed archive `tasks-axi show` with the same read bound the live backlog read uses (FM_BACKLOG_ROW_TIMEOUT_SECS via fm_run_timed, sanitising a non-positive/padded value to 10s, mirroring fm_backlog_row_show). A bound hit is treated as 124, not absence: task_show_archived returns 124 and prints a loud reason; archived_entry() propagates 124; verify_entry_durable() exits 124 on the archived-read bound (alongside its existing resolve 124 handling); verify_hold_durable() exits 124 rather than reading the row as absent. So verify can no longer hang and complete can no longer stay blocked while holding the origin metadata lock. ci-4 (User-level archive setting ignored): added captain_markdown_archive_setting(), which resolves the [markdown] archive key with tasks-axi's own source precedence — the backlog root's .tasks.toml first, then $HOME/.tasks-axi/config.toml — before the existing default of done-archive.md beside the backlog; a relative value still resolves against the backlog root, which I verified empirically matches tasks-axi (root config wins over home config; a relative home-config value resolves against the backlog root; default stays data/done-archive.md). tasks-axi exposes no command for the archive path, so re-parsing with matched precedence is the available approach. Added test_archived_answer_in_a_user_configured_archive_satisfies_the_gate, which sets the archive only in a fixture $HOME/.tasks-axi/config.toml and drives the whole hold→answer→retention→verify/complete/teardown flow with that HOME, asserting the answered call archived to the relocated location still satisfies the gate. ci-5 (repeated archive scans, P2): left unchanged per the instruction's opt-out. A clean reuse of the row archived_entry already found is not small: archived_entry runs inside a command substitution, so its TASK_SHOW_OUTPUT dies with the subshell and cannot be handed to verify_hold_durable without restructuring both to pass the loaded row through the current shell. The growth is bounded (O(archive size) per attested entry) and P2, so hardening it would add more machinery than the fix warrants. Unanswered archived closes and holds found nowhere still refuse (existing test coverage retained). Verification: `bash -n` clean; pinned ShellCheck 0.11.0 (`shellcheck -x bin/fm-captain-hold.sh`) reports 0 findings; tasks-axi archive-precedence behaviour confirmed by direct experiment. The full fm-captain-hold-lifecycle.test.sh suite (which exercises both the pre-existing archived-gate test and the new user-config test) is slow due to beads spawns and was still running at report time; recommend confirming it green before merge. ci-1/ci-2 need no code change (action_required fork workflow runs awaiting maintainer approval), per the recorded user decision

* no-mistakes(document): Document Done-archive reads under backlog row timeout

* docs: say only the archive row replay is bounded, not the archive scan

* no-mistakes(document): Move Done-archive rule to the complete section
…kunchenguid#7627)

* fix(bin): optionally read a PR with no CI checks as unverified, not checks green

nm_ci_checks_state maps the ci log marker "no CI checks reported - still
monitoring" to green, the same verdict as "all CI checks passed". A pull
request that no check ever ran against therefore surfaced as "checks green: PR
ready for review" (kunchenguid#3216).

Add a no-checks verdict for that marker, returned only in a home that creates
config/ci-no-checks-not-green. The run-step consumer reports working with a
detail naming the real condition, and the status-log override no longer
promotes a crew's own "checks green" report while that marker is the latest.
Without the flag every reading is unchanged.

Co-authored-by: Harlan Do <hieudtr8@gmail.com>

* no-mistakes(document): document ended-monitor reading under the zero-checks flag

* fix(bin): read a PR with no CI checks as unverified on every home

The ci log marker "no CI checks reported - still monitoring" now always
yields the no-checks verdict, so a pull request that no check ran against is
reported as working instead of checks green (kunchenguid#3216). The opt-in flag, its
docs section, and its skill entry are removed, the default-path test now
expects working, and the usage header and architecture note describe the
no-checks reading.

---------

Co-authored-by: Harlan Do <hieudtr8@gmail.com>
… test root-safe (kunchenguid#7650)

* no-mistakes(review): correct the counter-reset log line, the sentinel comment, and the root proof

Three findings the review step recorded but never applied, all in this branch's own change.

The clean-rollback branch of the counter-reset guard logged "cap state rolled back", copied
from the sibling marker-write branch. That is wrong here: the cap marker was already written
before this block and _wedge_cap_rollback only truncates the counter, re-arms the since-file
and clears write tracking - it never clears the markers. The window therefore stays silenced
for FM_CAP_HORIZON_SECS, so the one operator-facing line must say that rather than claim the
cap state was rolled back. The rollback behaviour is unchanged and no marker-clear call is
added: the marker is deliberately kept so the window stays quiet.

The sentinel short-circuit comment opened "from either route that has to defend cap state"
and then enumerated three routes. Wording only.

The new regression case injected its fault with chmod 444, which does not restrict root, so
under root the guard would never fire and the case would fail on its own exit-code
assertion. It now skips as root the way this suite's other mode-based faults do. The
assertions are untouched: as the ordinary user the case still proves the guard.

* no-mistakes(review): name both ways a capped window leaves the silence in the reset log line

The tier-0 message for a refused post-fire escalation-counter reset says the cap marker
stands, so the window stays silent until its horizon elapses. That names one release route
where there are two: wedge_episode_end drops the marker through _wedge_cap_markers_clear, so
any episode-ending route ends the silence early, and docs/configuration.md records deleting
the marker as the operator's way to re-engage a capped window at once. This tier writes no
sentinel, so the marker is the only thing left to remove.

The message now names the horizon, an episode-ending route, and the operator remedy, in one
line in the same shape and position as its sibling tier-0 messages. The rollback behaviour,
the marker handling and the tier are unchanged.

* no-mistakes(document): correct root-skip comment's mode-bit precedent in wedge-cap test
* fix(bin): resurface wakes appended while a handling successor is parked

The supervision host parks on the handling successor it starts for a wake
its engine handled, and a handling successor never resurfaced recovery, so
a row another script appended during the park (a captain inbox note from
the Basecamp integration) sat queued until an unrelated close - four hours
in the observed case.

A handling successor now records the newest queued sequence at its start
and closes once with the recovery wake while a newer row is still queued;
a newer row already consumed only advances that mark, so the predecessor's
delivered episode is still never re-announced.

Follow-ups, not built here:
- A row appended in the short window between a handled close and its
  successor's start (about a second) is below the successor's mark; if the
  engine handles that close without claiming the row, the row waits for the
  next close. Closing that window needs the host to treat rows left queued
  after a handled attended turn as main's, which is a separate host change.
- After a machine reboot the primary session does not restart on its own,
  so queued wakes wait until it is resumed by hand. Either auto-resume the
  primary session after boot, or alert the owner when the primary session
  is down while wakes are queued.

* no-mistakes(review): Close handling-successor startup wake race

* no-mistakes(test): Stabilize parked-host inbox wake regression timeout

* no-mistakes(document): Clarify handling-successor wake continuity guarantees

* no-mistakes(ci): Stabilized the flaky remote secondmate lifecycle test by extending the auto-relaunch watcher deadline from ~30 to ~60 seconds. The CI runner exceeded the old deadline by about 3 seconds; the changed production branch was not reachable in this fixture. `bash -n` and `git diff --check` pass. The full test was attempted but exceeded the 600-second local execution limit before reaching this case

* no-mistakes(ci): Fixed flaky concurrent Herdr recovery testing by retrying only documented session-lock contention after both competitors finish. Cleanup now handles fixture-owned read-only hooks. Targeted E2E passed; bash syntax and diff checks passed. Shellcheck reported only existing SC1091 source-following notices

* no-mistakes(test): Preserve successor wake boundaries across handoffs

* no-mistakes(test): Revert successor boundaries and drain teardown fixture wake

* no-mistakes(test): Notify main when downtime handback publication fails

* no-mistakes(test): Revert downtime handback notification changes

* no-mistakes(document): Correct stale supervision wake routing documentation

* fix(bin): stop the supervision host deadlocking and re-announcing handed-back closes

Two defects in this branch's handling-successor changes surfaced as the
"Behavior portable serial 2" shard hanging at its 30-minute limit.

A TERM that lands while the host settles a handled turn's successor handoff
left the host holding the wake-queue and recovery-marker locks; its exit
cleanup then waited forever on the queue lock it still owned. The cleanup now
releases those locks if this process owns them before releasing its grant.

When an accepted close turns main-only at its turn, the host hands it back and
leaves the successor running, but republished the episode as pending
downtime, so that successor re-announced the same close and exited. The
handback now republishes the episode as already announced; a later durable
append still opens a pending episode the successor recovers.

* no-mistakes(test): Park on successor after handling settlement failure

* no-mistakes(test): Use tab-delimited handled-settlement failure logs

---------

Co-authored-by: Caleb LeNoir <calebl@users.noreply.github.com>
* fix(bin): keep accented letters in pending-reply request summaries

fm_pending_reply_summarize deleted every byte outside printable ASCII, so a Portuguese summary lost its accents: "não faça a correção" was stored as "no faa a correo". It now removes control characters only. The 120-character cap counts UTF-8 characters from lead bytes, so it never splits a letter under any caller locale, including the C locale.

* no-mistakes(document): Documentar preservação de acentos nos resumos

---------

Co-authored-by: Alexandre Custodio <alexandre.custodio@soraschools.com>
…id#7149)

* Add explicit OpenRouter Jev dispatch resolution

* no-mistakes(document): Clarify dispatch documentation and remove stale duplicates

* chore: exclude local Stele binding from PR

* no-mistakes(document): Correct dispatch help and never-send documentation

* ci: retrigger CI after a timed-out test shard

* no-mistakes(document): Correct dispatch architecture documentation ownership
…imits (kunchenguid#6983)

Opt-in: fm-watch.sh publishes state/lane-capacity.json via bin/fm-capacity.sh --publish only while config/lane-capacity exists; adds beat pressure and keyed limits to fm-capacity.sh. Unconfigured fm-capacity output unchanged.
…#5359)

* fix(bin): transport contribution-input JSON through slurpfile, not argjson

bin/fm-fleet-snapshot.sh --contribution-input passed the full backlog and
task-record JSON to jq through --argjson. A single --argjson argument is
capped by the kernel's MAX_ARG_STRLEN (131072 bytes on Linux), so a large
enough backlog made the kernel refuse to exec jq while the script still
exited 0 with a broken result.

Transport both documents through temporary files and --slurpfile, matching
the pattern the default snapshot path already uses, and propagate a
non-zero exit when that jq invocation fails.

Fixes kunchenguid#5349

* test: narrow contribution-input regression to final assembly failure

The ARG_MAX transport is already fixed on main by kunchenguid#6830, so keep only the regression proving a failing final jq assembly exits non-zero.
* Fix Rex secondmate session lifecycle

* no-mistakes(review): Retire Rex secondmate sessions under the owning home identity

* no-mistakes(review): Limit Rex FM_ROOT label override to secondmate launches

* no-mistakes(document): Clarify Rex secondmate grouping and lifecycle documentation
…gins, and exit (kunchenguid#7336)

* feat(opencode): add OpenCode 2 support with version-gated launch and plugins

OpenCode 2 dropped the v1 server-plugin hook API, so the five fm.primary.*
plugins now export a default definition with an id and a setup function
alongside their legacy OpenCode 1 server export, backed by a new shared
lib/fm-opencode2.js helper; the turn-end guard, watch-arm, cd-check,
pretool-check, and sessionstart-nudge install their behavior from setup(ctx)
on OpenCode 2 and keep the server entrypoint for OpenCode 1.

fm-spawn probes the resolved opencode executable's --version once and branches
the launch, model flag, reasoning config, exit key, and busy-state plugin off
the detected major. A parsed major >= 2 takes the OpenCode 2 shape (no --model,
the model pinned through OPENCODE_CONFIG_CONTENT, a raw Ctrl-C exit, the
session.execution.* busy plugin); OpenCode 1.x and any undetected or
unparseable version fall back to the 1.x shape (--model, 1.x config, /exit)
everywhere. The worker busy-state plugin is always provisioned, but OpenCode 1
keeps the pre-port session.status/session.idle plugin so its busy-state wiring
is unchanged, and only a major >= 2 gets the session.execution plugin. Adapter
and supervision docs, OpenCode 2 live fixtures, and regression tests for the
unknown->1.x launch path and the version-gated busy-plugin shape are included.

* no-mistakes(review): gate OpenCode 2 busy plugin turn-end touch on notifyTurnEnd

* feat(opencode): launch detected OpenCode 2 workers with --standalone

OpenCode 2's default launch attaches to the shared background service, which
hosts plugins with the service's environment and ignores the pane's
OPENCODE_CONFIG_CONTENT. A private server keeps per-worker model, permissions,
FM_HOME, and lock ancestry correct. 1.x and undetected versions are unchanged.

Ported from kunchenguid#5143.

Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>

* fix(opencode): resolve the OpenCode 2 project location and treat every terminal turn as a turn end

OpenCode 2.0.x hands plugins the project only as ctx.location.project.directory
and takes ctx.session.prompt({ sessionID, text }), so the V2 setup path never
found its root or delivered a prompt. Interrupted and failed turns now end a
turn like a succeeded one, while a server shutdown does not.

Ported from kunchenguid#5143.

Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>

* test(opencode): add a token-free OpenCode 2 native plugin fixture to the live e2e

A private opencode serve plus a loopback provider proves the five primary
plugins load and act on OpenCode 2: startup nudge, cd and watcher-redirection
guards, turn-end guard, watcher arming across succeeded, interrupted, and
failed turns. The fixture declares the complete model capabilities object,
which OpenCode 2.0.11 requires and 2.0.22 accepts. FM_OPENCODE_LIVE_BINS runs
it across several OpenCode 2 executables. The credentialed half now picks its
run flags and session tables by detected major, so it keeps working on 1.x.

Fixture ported from kunchenguid#5143.

Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>

* feat(opencode): send one Enter when an OpenCode 2 launch leaves the brief unsent

OpenCode 2.0.11 prefills the --prompt brief without submitting it, while 2.0.22 submits it.
After launch, fm-spawn waits for the worker's busy-state plugin to report the turn start, sends one Enter only if it has not started and the composer is not empty, and fails the spawn if the turn never starts.

* docs(opencode): document the OpenCode 2 launch, delivery gate, and standalone primary homes

Records the 2.0.11 and 2.0.22 live evidence for the native plugin fixture and the post-launch brief delivery gate.

* no-mistakes(review): Trim OpenCode 2 adapter shapes and harden Ctrl-C exit

* no-mistakes(review): Record OpenCode 2 live seatbelt and exit evidence

* no-mistakes(test): Document OpenCode 2.0.22 wide-pane sidebar exit refusal limitation

* no-mistakes(document): Refresh OpenCode 2 exit-key and seatbelt payload docs

* test(opencode): verify OpenCode 1.x live with the dual-export plugins

Pin the live e2e TUI panes to the resolved opencode executable, and record
OpenCode 1.15.7, 1.18.20, and 1.18.31 evidence for plugin loading, the
turn-end guard, watcher continuity, and 1.x worker launch and exit.

* no-mistakes(review): Defer OpenCode 2 Enter on unknown composer; widen turn-end docs

* no-mistakes(document): Document OpenCode 2 delivery gate unknown-composer test outcomes

---------

Co-authored-by: Niranjan <nniranc@gmail.com>
Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t the most recent group member (kunchenguid#7662)

* fix(bin): resolve the tmux container from the caller's own session, not the most recent group member

* no-mistakes(test): Fall back when the $TMUX session id is stale

* no-mistakes(document): Document tmux container choice under grouped sessions
…er (kunchenguid#7647)

* fix(bin): read a fully dim Cursor placeholder as an empty composer

cursor-agent 2026.10.19 draws the composer glyph and its idle placeholder
as one SGR 2 run with no reverse-video cell, so ghost stripping empties the
row and the empty-content guard returned unknown. Relaunch and exit then
refused on every idle Cursor pane on tmux.

When a styled bare row strips to empty and the plain row is an agent glyph
followed by an anchored idle placeholder, read it empty. Bright typed text,
other dim text, and unstyled rows keep their existing verdicts.

Fixes kunchenguid#7413

* docs(verification): record the fully dim Cursor placeholder verdict and refresh command
… composer as empty (kunchenguid#6163)

* fix(bin): recognize a named Claude session's titled composer rule on cursorless backends

`claude -n <name>` right-aligns the session name in the rule above the
composer. Cursorless backends (Herdr) accepted only solid rules, so the
scan saw the lower rule alone, the idle composer read `unknown`, and
away-mode delivery to a named Claude primary deferred until return.

A rule carrying one right-aligned title now opens a separator pair. It
never closes one, the pair it opens counts only over an agent-glyph row,
and it is never a valid Pi pair, so Pi's verdict is unchanged.

The away-mode composer deferral now names which refusal it was:
unsubmitted text, or no composer shape recognized.

* no-mistakes(review): Reword composer deferral reason to cover busy harness

* no-mistakes(ci): ci-3 (Greptile, user chose to fix): The rule that must always hold is that Claude's titled opening rule is accepted for any non-empty session name that doesn't start with a space and doesn't contain the rule glyph `─`, including a multiword name. The only place that check lives is the title `case` in `_fm_composer_titled_rule_row` (bin/fm-composer-lib.sh). It now reads `case "$title" in ''|' '*|*─*) return 1 ;; esac`, as the user asked, and the comment above the function mentions multiword titles. I added regression fixtures to `test_matrix_claude_named_session_titled_rule` in tests/fm-composer-lib.test.sh, next to the existing single-word `Andrew` cases. They use the title `Andrew Chen` and check that an idle composer on herdr reads `empty` and a composer with typed text reads `pending`. Something you should know: the old pattern already accepted a title with a space in the middle. It only rejected a title ending in a space. So the new multiword fixtures also pass against the old code, and the bot's failure scenario did not actually happen for "Andrew Chen". What the fix really changes is that it drops the trailing-space rejection, as instructed. Checks run: tests/fm-composer-lib.test.sh and tests/fm-composer-ghost.test.sh both pass (exit 0, no failures), and `shellcheck -x` is clean on both changed files

* no-mistakes(review): Match unnamed-ghost fixture rule width to the partner rule
Merges kunchenguid/firstmate 8818a80 (22 commits since 9fe25bc).
One conflict: bin/fm-contributions.sh header comment. The fork's
task-data directory wording is kept, with upstream's enroll and owned
documentation added.
The fork's write_record groups a contribution record by the backlog
row's project, read from the snapshot input. Upstream's new enroll
subcommand (kunchenguid#7634) skipped get_input, so every enroll exited before
writing and the PR health monitor never owned a branch PR. enroll now
reads the input first, as retire already does.

tests/fm-pr-health.test.sh resolves the contribution record through
fm_test_task_dir instead of the flat data/<id>/ path.
@jbalke
jbalke merged commit b2ba74b into main Oct 23, 2026
36 of 37 checks passed
@jbalke
jbalke deleted the fm/fm-reconcile-upstream-2026-10-23 branch October 23, 2026 12:36
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.