fix(send): treat opencode busy-queued composer state as submitted#1
fix(send): treat opencode busy-queued composer state as submitted#1KostadinP wants to merge 167 commits into
Conversation
…uid#93) * feat(send): mark firstmate->secondmate requests for status-path reply fm-send now prepends a distinct from-firstmate marker (new bin/fm-marker-lib.sh: the label [fm-from-firstmate] followed by ASCII unit separator 0x1f) when the resolved target is a bare fm-<id> whose meta records kind=secondmate. The marker reuses the afk untypable-separator concept but is deliberately distinct from the daemon's bare leading 0x1f, so it never conflates with a secondmate's own afk escalation marker. A secondmate is itself a firstmate, so a relayed request lands in its own chat, which the main firstmate never reads. The secondmate charter (fm-brief.sh) and AGENTS.md now document the recognize-and-respond-via-status contract: a marked request is answered via the status file (or a doc plus a status pointer), never chat-only; an unmarked message stays conversational captain intervention. Crewmate/scout targets, explicit session:window targets, and the --key path are unchanged. Backbone (watcher, daemon, tmux-lib, afk) untouched. Hermetic tests cover marked vs unmarked sends and the exact marker bytes. * no-mistakes(document): Sync secondmate marker docs
* fix(teardown): recognize squash-merged work as landed fm-teardown refused to tear down a worktree whose PR was squash-merged and whose head branch was then deleted - the most common GitHub flow - because its safety check used commit reachability (HEAD --not --remotes) rather than whether the work actually landed. A squash merge writes one new commit on the default branch, so the branch's own commits are on no remote-tracking branch, and auto-delete-on-merge drops the head ref too; reachability was non-empty and teardown false-refused merged work. For a normal ship task whose commits are not remote-reachable, before refusing, treat the work as landed if either its PR is merged (resolved from meta pr= or the branch name via gh-axi; authoritative for squash, rebase, and merge alike, and surviving branch deletion) or its content is already in the up-to-date default branch (a 3-way merge-tree that adds nothing the default branch lacks - robust to the default having advanced past the merge-base). Dirty worktrees and genuinely unlanded work still refuse; a gh lookup error falls back to the content check and, if that is inconclusive, refuses (fail-safe, never silently allows). Fork, local-only, scout, secondmate, and --force paths are unchanged. Tests cover squash-merged+deleted-branch (allow), genuinely unlanded (refuse), dirty (refuse), normally pushed (allow), content-in-default fallback (allow), and gh-error+content-absent (refuse, fail-safe). * no-mistakes(review): Guard teardown against stale PR proof * no-mistakes(review): Verify teardown PR heads * no-mistakes(document): Sync teardown landing docs * no-mistakes: apply CI fixes
* fix: harden no-mistakes validation contract against gate-park and self-fix duplication Rewrite the no-mistakes validation block scaffolded into every ship brief so a crewmate knows the right move at each gate: the pipeline owns every fix (including the fix for a real bug review finds in the crewmate's own code); respond, never self-implement/abort/re-run; the ask-user loop end to end (feed the decision back via axi respond, do not hand-fix); process every return (backgrounding ok, never idle-wait for auto-advance); avoid --yes (it auto-resolves ask-user with zero escalation); and review findings always gate (review auto-fix is disabled). Add the supervisor heuristic in AGENTS.md: firstmate keys off the no-mistakes run step status - running/fixing/ci means working, awaiting_approval/fix_review means parked (surfaced as awaiting_agent: parked <duration> on axi status) - not shell liveness, plus a self-fix-duplication red flag (hand-commits/abort/re-run mid-validation). Resolves the gate-park-deadlock and self-fix-duplication failure modes. * no-mistakes(document): Sync validation docs * no-mistakes(lint): Lint checks clean
* fix(watch): tighten watcher re-arm discipline and assert liveness on drain The watcher supervision chain could silently lapse: re-arms were bundled at the tail of multi-command calls (where fm-watch-arm no-ops when a cycle is briefly still alive, so no fresh cycle got established), and text-only "holding" turns ran no supervision script, so fm-guard's liveness banner - which only fires when a guarded script runs - never triggered. AGENTS.md section 8: make the re-arm rules unambiguous - keep exactly one live cycle while work is in flight; re-arm after each FIRE (a completed arm task carrying a wake reason) and never churn on a healthy/started no-op; run fm-watch-arm standalone, never bundled; never end a turn blind, holds included. Existing material (singleton lock, beacon, guard banner, afk exception) is preserved. fm-wake-drain.sh: assert watcher liveness after draining by reusing fm-guard.sh's existing graced, beacon-based banner, so a lapse also surfaces on a plain drain-and-handle turn. Called after the queue is emptied so the guard never re-prints its own queued-wakes notice, and best-effort so it never changes the drain's exit status. The grace beacon keeps it silent right after a normal fire and warns only on a stale-beyond-grace lapse with work in flight. Watcher core (fm-watch.sh, lock, beacon-touch, wake-queue) untouched. tests: keep the worktree-tangle check inert across the drain-invoking suites (wake-helpers points FM_ROOT at a non-git dir, the same trick the direct fm-guard.sh tests use), and add a regression test asserting the drain warns on a lapse and stays silent right after a fire. * no-mistakes(document): Document drain-time watcher liveness
…id#102) * fix(brief): slim no-mistakes contract to pointer + firstmate wrapper no-mistakes v1.31.2 self-documents the validation mechanics in its own SKILL.md (refreshed from the binary on init) and live axi help output, including the review-always-gates, no-abort/rerun-mid-run, and never-idle-wait facts. fm-brief.sh's no-mistakes-mode Definition of done was restating all of that, a drift hazard since the brief lives in a different repo from the version-matched SKILL.md. Collapse the duplicated mechanics block to a ~3-line pointer at no-mistakes' own guidance and keep only the firstmate-specific wrapper: the done handshake, ask-user escalation to firstmate via rule 6, the --yes captain stance, and the CI-green reporting line. * no-mistakes(review): Enforce no-mistakes bootstrap version floor * no-mistakes(document): Sync no-mistakes docs * no-mistakes(lint): Fix ShellCheck version parsing
* fix(send): settle codex $skill popup before submit
A `$<skill>` invocation (e.g. $no-mistakes) opens codex's $-autocomplete
popup; submitting too fast lets it swallow the Enter so the invocation
never lands - biting every pipeline trigger to a codex crew/secondmate.
Mirror the existing `/` slash-popup handling: give a `$...` message the
1.2s popup-settle before the (retried) Enter, but scope it to
harness=codex (read from the target's meta) so `$`-prefixed plain text
("$5/month", "$HOME") to claude/opencode/pi keeps the 0.3s fast path. An
explicit session:window target has no meta -> harness unknown -> treated
as non-codex. The retried Enter in fm_tmux_submit_core still backs the
settle up; the `/` case, --key path, marker, and meta-resolution
contract are unchanged.
Record the codex $-popup fact in the harness-adapters skill, and add a
per-harness settle-selection test (codex $ ->1.2, claude $ ->0.3,
explicit $ ->0.3, any / ->1.2, plain ->0.3).
* no-mistakes(document): Sync fm-send popup docs
* feat(supervise): deterministic crew current-state helper Add bin/fm-crew-state.sh, a deterministic one-line read of a crew's CURRENT state that ends the stale-status-line friction in supervision. The status log is an append-only wake-event log, so tail -1 reports the last event, not the current state: after firstmate resolves a needs-decision/blocked and the crew silently resumes, the log stays stale. The helper takes the active no-mistakes run-step as the source of truth (attributed to the crew's branch, via axi status then the run list), reconciles a possibly-stale needs-decision/blocked log line against it (flagging it superseded when the run has resumed), and falls back to the pane busy-signature + status log when no run is active. Handles scout/secondmate kinds and torn-down/missing crews gracefully. Reuses fm-tmux-lib.sh busy detection and meta parsing; fully deterministic (run-step/pane/log reads only, no heuristics, no LLM). Wire AGENTS.md sections 7 and 8 to read current state via the helper and to stop inferring state from a tail of the status log; the status file stays the wake-event log. Watcher core, status-file/brief contracts, and wake/dedup machinery are unchanged. Add tests/fm-crew-state.test.sh covering run-step authority, superseded stale log lines, genuine-parked, cross-branch attribution, pane and status-log fallback, scout skip, and torn-down/missing-meta cases. * no-mistakes(review): Fix dead-window crew state fallback * no-mistakes(review): Harden crew state liveness checks * no-mistakes(review): Harden crew state gate handling * no-mistakes(review): Harden crew state gate parsing * no-mistakes(document): Document crew state helper * no-mistakes: apply CI fixes * fix(crew-state): keep run-step authoritative over a closed pane The dead-window guard ran before the no-mistakes run lookup, so a finished crew whose agent had exited and closed its window reported 'unknown' instead of its authoritative run-step state (e.g. done) - the normal gap between a crew completing and teardown, and a regression against the helper's core principle of judging by the run-step, not the shell. Move the pane-readability guard into the no-run fallback path so it runs only after the run-step lookup: a crew with a run reports its run-step state regardless of pane liveness, while a genuinely runless dead window still reports unknown rather than trusting a stale log. Replace the test that encoded the regression with two that pin the correct behavior: a closed pane with a terminal run reports done, and a closed pane with an active run reports working. * no-mistakes(document): Sync crew-state docs
* feat(watch): always-on wake triage absorbs benign wakes in bash Factor the afk daemon's classify_signal/classify_stale/heartbeat-scan into a shared bin/fm-classify-lib.sh used by both the always-on watcher and the daemon. fm-watch.sh now loops internally: it classifies each wake and absorbs the benign majority (working: signals, bare turn-ended, non-terminal stale, no-change heartbeat) by advancing the suppression marker and logging, without queuing or exiting; it writes the durable queue and exits only on an actionable wake (captain-relevant signal, any check, terminal stale, a non-terminal stale that persists past FM_STALE_ESCALATE_SECS, or the heartbeat fleet-scan backstop). So firstmate re-arms once per actionable event instead of once per wake. Safety preserved: singleton lock + beacon (touched every poll including while absorbing), durable queue for actionable wakes, kind=secondmate stale-skip, per-task check polling, heartbeat backoff, bounded wedge latency. While state/.afk exists the daemon owns triage and the watcher reverts to one-shot, so the two never double-triage. AGENTS.md section 8 + afk skill updated; tests added. * no-mistakes(review): Captain, repair watcher triage edge cases * no-mistakes(document): Sync watcher triage docs * no-mistakes: apply CI fixes
…unchenguid#87) The firstmate side of "listen on X and reply", shipped inside the repo so every user has it but INERT until they opt in (a non-empty FMX_PAIRING_TOKEN in .env). Purely additive: the watcher backbone (fm-watch.sh, fm-watch-arm.sh, fm-wake-lib.sh) and the afk daemon (fm-supervise-daemon.sh, afk skill) are untouched. - bin/fm-x-poll.sh: one short-poll of GET /connector/poll; hard no-op without a token; requires non-empty text; stashes the full mention (incl. in_reply_to conversation context) to state/x-inbox/<id>.json behind a path-traversal guard; prints "x-mention <id>" (or a rate-limited "x-mode-error ...") for the watcher. - bin/fm-x-reply.sh: POST /connector/answer; bearer token via a 0600 header file (never argv); reply via --text-file/stdin so mention text is never inlined into a shell command. Long replies auto-split into a premium-independent numbered "(k/n)" thread (codepoint-aware, capped); single tweet stays unnumbered. Wire: {request_id, text}, plus {texts:[...]} for a thread. FMX_DRY_RUN previews to state/x-outbox and posts nothing. - bin/fm-x-lib.sh: .env/env config (token, relay default, dry-run, max chars, thread cap; env wins over .env) and the thread splitter. - bin/fm-bootstrap.sh: .env-presence activation - drop the check shim + 30s cadence config on opt-in, remove on opt-out, idempotent, silent off. - .agents/skills/fmx-respond: public-safe answer playbook - drain inbox, judge follow-up worthiness (skip pure acks), conversation continuity via in_reply_to, concise by default, dry-run aware. - AGENTS.md section 14 + README/CONTRIBUTING/docs; tests/fm-x-mode.test.sh (hermetic: fake curl, real jq).
* fix(fmx): X-mode mentions are captain instructions - act, then reply Owner-only relay routing means every routed mention is from the firstmate's own owner, so fmx-respond no longer frames the asker as a stranger and may address them as captain. Enabling X mode is the standing authorization, so replies are composed and posted autonomously with no per-reply confirmation; dry-run stays the only non-posting path. A mention's request is a real captain instruction to act on, not merely to reply to: the drain loop now classifies each mention as an actionable request (run firstmate's normal lifecycle - intake, backlog, dispatch, scout, ship - then report the outcome), a question (answer from fleet state), or a pure acknowledgment (skip). The public channel keeps the yolo carve-out: anything destructive, irreversible, or security-sensitive is flagged to the captain through the trusted channel first, never executed straight from a mention. Public-safety (outcomes only, no secrets/internals), the untrusted-in_reply_to caution, and the skip-acknowledgment judgment remain intact. AGENTS.md §14 reflects the owner identity, autonomous answering, and act-on-requests carve-out. * no-mistakes(review): Captain: clarify X-mode consent safeguards * no-mistakes(review): Captain: document X-mode action consent * no-mistakes(document): Captain, sync X-mode docs
* fix(fleet-sync): self-heal safe detached drift, loudly flag stuck clones A pooled clone that drifts off its default branch was silently skipped forever by both the post-merge teardown sync and bootstrap fleet-sync, falling further behind on every merge with only an easy-to-miss skip line. - Auto-recover the one safe case: a clean, detached HEAD that is an ancestor of origin/<default> and whose <default> is free to check out is re-attached and fast-forwarded, reported as 'recovered:'. - Every other off-default state (dirty, non-default named branch, detached with unique commits, diverged) is left untouched and reported as a quantified 'STUCK: ... N commits behind ... - needs attention' warning instead of a quiet drift. Nothing is forced, stashed, or discarded. - Relay the new recovered:/STUCK: outcomes through bootstrap FLEET_SYNC lines; document both in AGENTS.md section 3. - Add tests/fm-fleet-sync.test.sh covering recover, every stuck variant, ordinary fast-forward, already-current, local-only/no-origin skips, the whole-fleet form, and the bootstrap relay. * no-mistakes(review): Guard detached recovery from diverged local defaults * no-mistakes(document): Sync fleet refresh docs * no-mistakes: apply CI fixes
* feat(x-mode): X-mention completion follow-up flow Acknowledge an actionable X mention first, do the work, then post one follow-up reply when it completes. - fm-x-reply.sh: add --followup mode posting to the relay's /connector/followup endpoint; reuses thread-split, payload shape, dry-run (with a self-describing endpoint marker), and never-inline safety. Answer path unchanged. - fm-x-link.sh: link a spawned task to its originating mention via x_request/x_request_ts in state/<id>.meta (atomic, preserves other lines). - fm-x-followup.sh: --check detection plus post-and-clear on terminal completion; honors the 24h window (skip+prune past it), keeps the link on a failed post for retry. - fm-x-lib.sh: shared meta link get/set/clear helpers. - Docs: fmx-respond reads as one ack-first -> act -> follow-up flow; AGENTS.md §14 + supervision pointer document the link, completion follow-up, and 24h public-safe window. - Tests: cover --followup endpoint/payload/dry-run, link, and the followup helper; shellcheck clean. * no-mistakes(review): Captain, fix atomic X meta rewrites * no-mistakes(document): Document X completion follow-ups
…id#120) * feat(x-mode): dismiss skipped mentions at the relay The relay now exposes POST /connector/dismiss: acknowledge a pending mention without replying - it drops the request, posts nothing, and stops re-offering it. Wire firstmate to use it on the skip path so a deliberately unanswered mention no longer churns every poll and times out to the relay's "offline" auto-reply. - bin/fm-x-dismiss.sh: new client modeled on fm-x-reply.sh. POSTs {request_id} (no body) to /connector/dismiss with the bearer; echoes the request_id on 2xx, exits non-zero on non-2xx/transport failure. Honors FMX_DRY_RUN (records the would-be POST to state/x-outbox/ with an endpoint:"dismiss" marker, posts nothing) and rejects unsafe request_ids. - fmx-respond skill: the skip path now calls bin/fm-x-dismiss.sh before clearing the inbox file; answer and follow-up paths unchanged. - AGENTS.md section 14: documents that a skipped mention is dismissed at the relay, not just locally cleared. - tests: dismiss posts {request_id} to /connector/dismiss with the bearer and echoes it; dry-run records and posts nothing; non-2xx and transport failures exit non-zero; unsafe id and bad args rejected. * chore(no-mistakes): run the bash suite directly as the test step The test step had no configured test command, so it delegated to an agent; that agent-driven run crashed the no-mistakes daemon mid-step on this repo. Configure commands.test to run the firstmate behavior suite deterministically instead, mirroring .github/workflows/ci.yml: iterate every tests/*.test.sh, run each, and fail the step if any exits non-zero. This removes the agent from the test step entirely (no crash) and makes the gate's test baseline match CI. Same pattern myfirstmate uses (commands.test: mix deps.get && mix test). * no-mistakes(review): Fix X dismiss docs and gate preflight * no-mistakes(document): Document X dismiss and gate tests
…unchenguid#126) * feat(watcher): absorb wakes only when the crew is provably working The no-verb triage path (a bare turn-end, a working: note, a non-terminal stale) used to be benign by default and surfaced only on a captain-relevant status verb. A crew that finished but reported through interactive pane menus (no done: status) had its final turn-end absorbed, so firstmate was never woken and the finish was missed. Invert the rule: absorb a no-verb turn-end or non-terminal stale ONLY when the crew shows positive evidence it is still working - its no-mistakes run for its branch is in an actively-running step, or its pane shows the harness busy signature. Otherwise surface it so firstmate peeks (done, waiting, or wedged). - fm-classify-lib.sh: add crew_is_provably_working (reuses fm-crew-state.sh, no run-step duplication) and signal_crew_provably_working; FM_CREW_STATE_BIN override for tests. - fm-watch.sh: signal path surfaces a no-verb wake whose crew is not provably working (costly check runs only on the no-verb, non-afk path); non-terminal stale surfaces immediately when not provably working, else absorbs with the wedge timer (run-step read only on first sight of a stale hash). - afk path unchanged: the watcher stays one-shot and skips the provably-working read; the daemon keeps its bounded-latency stale backstop. - tests: cover every required semantic (mid-pipeline absorb, finished/parked surface, no-running-pipeline idle surface, busy absorb, captain-verb surface) as classifier unit tests and behavioral watcher runs; queue-safety test for the new immediate-surface stale path. - AGENTS.md section 8: document absorb-only-when-provably-working. * no-mistakes(document): Sync watcher documentation
* feat(harness): add grok (Grok Build) as a verified crewmate adapter Empirically verified against grok 0.2.73 and encoded across the machinery: - fm-harness.sh: detect grok via GROK_AGENT=1 env marker (grok does not set CLAUDECODE) and `grok` command-name ancestry. - fm-spawn.sh: grok launch template (`grok --always-approve "$(cat BRIEF)"`, fully autonomous, no permission gate) and a turn-end Stop hook. grok only loads project hooks after a manual folder-trust grant, so the hook is a single firstmate-owned global hook (~/.grok/hooks/fm-turn-end.json, always trusted) that is a guarded no-op unless the workspace holds a per-task .fm-grok-turnend pointer; fm-spawn drops that gitignored pointer naming state/<id>.turn-ended. Hook stays outside the worktree, needs no trust grant. - fm-watch.sh + fm-tmux-lib.sh: grok busy signature `Ctrl+c:cancel` (the mid-turn cancel hint; ASCII, present iff a turn runs). - harness-adapters skill: grok facts section (busy, exit=Ctrl+Q x2, interrupt=Ctrl+C, skill invocation /<skill>, resume) and /no-mistakes form. Gating question confirmed: grok invokes /no-mistakes and drives a real no-mistakes axi run, so grok is usable for no-mistakes-mode tasks. End-to-end verified through fm-spawn: autonomous launch past the dir picker into the worktree, brief processed, busy->idle and turn-end signal detected, fm-send steer lands, clean Ctrl+Q exit and teardown. config/crew-harness is left unchanged; this only makes grok available as a verified option. * no-mistakes(review): Captain, harden Grok hook lifecycle * no-mistakes(review): Captain, make Grok harness test executable * no-mistakes(review): Captain, bound Grok pointer reads * no-mistakes(test): Captain, harden crew-state and watcher-lock timing * no-mistakes(document): Document Grok harness support
* feat(harness): split secondmate harness and inherit primary config into secondmate homes Add config/secondmate-harness so secondmates can run on a different adapter than crewmates. fm-harness.sh gains a `secondmate` mode resolving the chain config/secondmate-harness -> config/crew-harness -> own; `crew` mode is unchanged. fm-spawn resolves a --secondmate launch through that mode (durable: every respawn re-resolves), while an explicit per-spawn harness arg still wins and the unverified-adapter guard still holds. Add a generic, extensible inheritable-config mechanism (fm-config-inherit-lib.sh) that pushes the primary's declared LOCAL config into each secondmate home's config/ at secondmate spawn and on the bootstrap secondmate sweep. Exactly one item is wired today: config/crew-harness, so a secondmate's own crewmates use the primary's setting. Primary-authoritative (re-pushed every convergence, mirrors absence); config/secondmate-harness is deliberately not inherited since secondmates never spawn secondmates. config/ is gitignored, so this is a copy separate from the tracked-files fast-forward. Update AGENTS.md (layout, bootstrap, harness, spawn), the harness-adapters skill, docs/scripts.md, and .gitignore. New tests cover secondmate resolution and fallback, spawn/respawn honoring config/secondmate-harness, config propagation on spawn and sweep, the unverified-adapter guard, and backward compatibility. * no-mistakes(review): Surface inherited config propagation failures * no-mistakes(review): Harden inherited config propagation * no-mistakes(review): Document literal harness inheritance requirement * no-mistakes(document): Document secondmate harness config
* feat(backlog): default to tasks-axi backend * no-mistakes(document): Sync backlog backend docs
… /tmp (kunchenguid#36) * fix(spawn): set per-task GOTMPDIR so interrupted Go builds don't leak /tmp Go's GOTMPDIR is unset, so every go build/test creates numbered /tmp/go-build* dirs. Go cleans them on a clean exit but LEAVES THEM when interrupted (signal, timeout, OOM, full disk), accumulating and filling the disk over time. Give each task its own temp root at /tmp/fm-<id>/ with Go's build temp nested at gotmp/. fm-spawn creates the dir (Go won't mkdir GOTMPDIR), exports GOTMPDIR into the crewmate pane so the agent and child processes inherit it, and records tasktmp= in meta. fm-teardown reads tasktmp= and removes the whole root on cleanup, deterministically. GOTMPDIR (not TMPDIR) is the targeted knob: TMPDIR is too broad (affects every program's temp). The nested root is extensible: other per-task temp can live under /tmp/fm-<id>/ later. Backward compat: tasks spawned before this change have no tasktmp= in meta; teardown tolerates the empty value as a no-op. The daily fm-disk-cleanup.sh cron remains a safety net for any pre-fix stray dirs. * fix(tests): silence SC2016 for literal grep -F patterns in fm-gotmp test The structural grep -F assertions deliberately match literal $TASK_TMP in the fm-spawn source; add per-line shellcheck disable=SC2016 (the codebase's existing pattern, e.g. bin/fm-spawn.sh) so CI lint passes. * no-mistakes(document): docs: document tasktmp= meta field for per-task GOTMPDIR --------- Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
* fix(teardown): accept landed squash-merge PR heads * no-mistakes(document): Document teardown landing behavior * no-mistakes: apply CI fixes * fix(test): pass explicit teardown git identity
* feat(dispatch): add dynamic crew profiles * no-mistakes(review): Captain, document dispatch profile inheritance * no-mistakes(review): Captain, guard stale dispatch inheritance * no-mistakes(document): Sync dispatch profile docs * no-mistakes: apply CI fixes
* Harden crew dispatch profile enforcement * no-mistakes(document): Captain, synced crew dispatch docs
* feat(config): add live secondmate config push * no-mistakes(document): Document config push behavior * no-mistakes(lint): Clean changed shell lint * no-mistakes: apply CI fixes
* feat(x): add image attachments to reply helpers * no-mistakes(review): Stream X image replies safely * no-mistakes(review): Captain, clean X reply temp tracking * no-mistakes(document): Document X reply image support
* fix(teardown): make landed-check robust when no pr= was ever recorded fm-teardown.sh's squash-merge landed-check already falls back to discovering a merged PR by branch name when state/<id>.meta has no recorded pr=, but nothing guaranteed pr=/pr_head= actually got recorded on a yolo-authorized merge - the "checks green" trigger that normally runs fm-pr-check.sh never fires on repos with no PR CI, so a merge done via a bare `gh-axi pr merge` silently skips it. Add bin/fm-pr-merge.sh as the one path for merging a task's PR: it always runs fm-pr-check.sh first, so pr=/pr_head= land in meta as part of the merge itself regardless of any CI signal. Document both the existing branch-name discovery fallback and the new merge path in AGENTS.md, and add regression coverage for the no-pr=-recorded landed scenario and for fm-pr-merge.sh's record-then-merge behavior. * no-mistakes(review): Guard PR merges on task metadata * no-mistakes(document): Document PR merge wrapper * no-mistakes: apply CI fixes
* Fix fm-pr-merge.sh to parse PR URLs for gh-axi gh-axi pr merge expects a PR number and --repo, not a full GitHub URL. Parse the URL, default to --squash when no merge method is passed, and fail fast on malformed URLs. Tests cover parsing, defaults, and refusal. * no-mistakes(review): Harden PR merge validation * no-mistakes(review): Harden PR merge URL guards * no-mistakes(document): Document PR merge URL handling * no-mistakes(lint): Clean shell lint
* feat: pin secondmate model/effort in config/secondmate-harness Extend config/secondmate-harness's format to an optional "<harness> [<model>] [<effort>]" line so a secondmate can be durably locked to a concrete model/effort in the same file, without adding a new config file. A bare harness-only file behaves exactly as before. fm-harness.sh gains secondmate-model/secondmate-effort accessors; fm-spawn.sh populates MODEL/EFFORT from them on every secondmate spawn (including respawns) unless the caller passed an explicit --model/--effort. * no-mistakes(review): Fix secondmate override pin precedence * no-mistakes(document): Document secondmate harness pins
* feat(bin): extract tmux runtime behind a backend interface (P1) Add bin/fm-backend.sh (selection, meta helpers, selector resolution, dispatch) and bin/backends/tmux.sh (the tmux adapter), then route fm-send.sh, fm-peek.sh, fm-watch.sh, fm-spawn.sh, and fm-teardown.sh through them. Every default tmux command sequence, meta shape, and printed output stays byte-identical: missing backend= still means tmux, and a default spawn never writes backend=tmux. Adds a --backend flag (tmux-only for now) and FM_BACKEND/config/backend selection, refusing any unimplemented backend loudly. Names the watcher's poll loop as the default event-source implementation over the backend's pull primitives, per the herdr-addendum's events-as-the-core- abstraction direction, without changing its behavior. Verification: fake-tmux/treehouse old-vs-new command-log conformance tests for send/peek/spawn/teardown, a real-tmux smoke test for the adapter, and the full existing suite passing unmodified (bar two fixture-only additions in fm-gotmp.test.sh for the new sibling scripts). * no-mistakes(review): Captain, harden backend baseline resolution * no-mistakes(review): Captain, ignore and document backend config * no-mistakes(review): Captain, make backend tests executable * no-mistakes(document): Sync runtime backend documentation
* feat(bin): add experimental herdr runtime backend (P2) Implements bin/backends/herdr.sh (session-provider adapter, D3: treehouse stays the worktree provider) wired through fm-backend.sh's dispatch, with --backend herdr / FM_BACKEND=herdr / config/backend selection, a version/protocol gate at spawn, semantic busy-state detection via herdr's agent.get (fm-watch.sh and fm-crew-state.sh consult it before falling back to the existing tmux pane-regex path), and label-based recovery discovery. Container shape (D4) decided empirically: tab-per-task in one "firstmate" workspace, mirroring tmux's one-session-many-windows model. Found and fixed two real herdr v0.7.1 bugs during verification: `pane read --lines N` returns empty for small N (worked around by over-fetching and trimming locally), and `pane get`'s cwd field is frozen at pane-creation time (fixed to read foreground_cwd instead, needed for fm-spawn's worktree- discovery poll after `treehouse get`). Also fixed a pre-existing bug in tests/fm-backend.test.sh's old-vs-new fixture that was silently missing fm-backend.sh/bin/backends/ from the old bin/ shim. Full empirical verification, the D4 decision evidence, and a real end-to-end run (spawn/steer/peek/done/merge-local/teardown, including confirming teardown refuses before the merge) are recorded in docs/herdr-backend.md. The entire existing tmux conformance suite stays green. * no-mistakes(review): Fix Herdr supervision recovery gaps * no-mistakes(review): Captain, fix Herdr stale recovery gaps * no-mistakes(review): Document Herdr composer primitive candidate * no-mistakes(review): Captain, harden Herdr stale recovery and tests * no-mistakes(document): Sync herdr backend docs * no-mistakes: apply CI fixes
* feat(bin): auto-detect runtime backend from HERDR_ENV/TMUX markers fm_backend_name now falls through to runtime auto-detection between config/backend and the hard tmux default: a firstmate running natively inside herdr (HERDR_ENV=1) now spawns crewmates into herdr by default, mirroring how harness detection already works in fm-harness.sh. Nesting resolves innermost-first (tmux wins over a nested herdr pane). Explicit --backend/FM_BACKEND/config/backend settings always win over detection. Selecting herdr via auto-detect prints a loud stderr notice; auto-detecting tmux stays silent so the unconfigured default path is unchanged. * no-mistakes(review): Captain, pin tmux tests and backend docs * no-mistakes(document): Sync backend autodetect docs * no-mistakes: apply CI fixes
* feat(stow): add operational-memory learnings convention and /stow skill Add data/learnings.md as the fleet-local operational-learnings home, a knowledge-routing table in AGENTS.md, and a user-invocable /stow skill that sweeps a session for uncaptured durable knowledge and files it to the right disk home before a reset. * no-mistakes(review): Fix stow backlog note command * no-mistakes(document): Document stow memory routing
…nchenguid#649) * Clarify concise secondmate registry contract * no-mistakes(review): Expand secondmate registry boilerplate coverage * no-mistakes(document): Point route docs to owner
…kunchenguid#654) * fix(bin): strip quotes on blocked_by in decision-hold resolve tasks-axi quotes multi-entry blocked_by as "a,b,c", so the comma-boundary membership test only matched middle elements. Strip surrounding quotes before matching so first and last hold ids resolve correctly. * no-mistakes(document): Refresh decision-hold regression evidence
* feat(secondmate): inherit shared captain preferences * no-mistakes(review): Honor shared captain data overrides * no-mistakes(review): Honor bootstrap data override registry * no-mistakes(document): Refresh shared inheritance docs * no-mistakes(document): Clarify inherited local-material docs
* feat(spawn): gate local agent secret injection * fix(spawn): align final Keychain slot * no-mistakes: apply CI fixes
* test: isolate herdr autodetect smoke session * no-mistakes(review): Restored autodetect smoke gate bypass * no-mistakes(test): Harden Herdr lab provisioning * no-mistakes(document): Refresh Herdr lab docs
…unchenguid#668) This reverts commit c27135c.
* fix(pi): distinguish stale locks when arming watcher * no-mistakes(test): Stabilize watcher extension async waits * no-mistakes(document): Document Pi lock recovery
* fix: accept secondmate as house vocabulary * no-mistakes(test): Update captain vocabulary contract test * no-mistakes(document): Align secondmate documentation vocabulary
…uid#686) * fix: parse secondmate home after pre-field parentheses Registry summaries often include parentheticals before the structured (home: ...) field. Match that field with a greedy prefix so handoff no longer reports "has no home" for those entries. * no-mistakes(document): Refresh handoff test comments
* feat: add native session-start nudges * no-mistakes(document): Document nudge script inventory
…nguid#688) Relabel absent-captain and related domain defaults wording so it names the firstmate repo rather than treating "template" as this domain's identity label. Keep the design-tenet "shared template" statements and unrelated launch/PR-poll template uses unchanged.
…nsion (kunchenguid#205) * fix(bin): use set -u-safe empty-array expansion in pr-merge and spawn Expanding "${arr[@]}" on an empty array under set -u fails on bash < 4.4 (notably macOS bash 3.2). Quote the portable "${arr[@]+"${arr[@]}"}" idiom in fm-pr-merge and fm-spawn batch dispatch so empty arrays expand to nothing. Co-authored-by: Cursor <cursoragent@cursor.com> * test(brief): harden fm-brief regression coverage for parse and scaffolds Tighten bash -n checking, pin literal backtick rendering in the no-mistakes DOD wording assertion, and keep a scout/secondmate scaffold smoke test so the Co-authored-by: Cursor <cursoragent@cursor.com> kunchenguid#166 apostrophe regression cannot return unnoticed. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
…nchenguid#693) * fix: make watcher supervision continuous * no-mistakes(review): Bound watcher retries and log attached signals * no-mistakes(review): Add bounded successor-recovery wake fallbacks * no-mistakes(review): Prevent overlapping successor-arm retries * no-mistakes(review): Resume supervision after late arm closes * no-mistakes(review): Bind OpenCode recovery to attempted arm * no-mistakes(test): Synchronize peer beacon regression fixture * no-mistakes(test): Synchronize Pi and OpenCode late-close lifecycle fixtures * no-mistakes(document): Captain: document watcher successor protocol behavior * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes
* fix: always fetch PR head for review diffs Prefer a freshly fetched refs/pull/<n>/head over a reachable recorded pr_head= so reviewers never hold a merge over a "missing" fix that already landed on the remote PR. Recorded SHA is offline fallback only; local branch is last resort with a warning. Store the tip under refs/fm-review/ so a later base-branch fetch cannot clobber the compare tip via FETCH_HEAD. * no-mistakes(test): Isolate session-start nudge tests from gate state * no-mistakes(document): Correct review-diff documentation
…and skills (kunchenguid#736) * docs: resolve five contract contradictions * no-mistakes(test): align owner-pointer assertions with reworded docs; skip absent shellcheck
* docs(harness): reverify grok exit command * no-mistakes(test): Correct Grok exit resume attribution
* fix(watcher): bound stale wakes for exited paused crew * no-mistakes(review): Gate pause suppression on confirmed agent death * no-mistakes(test): Fixed stale pause cadence * no-mistakes(document): Document dead-agent hold cadence
…id#744) * fix(supervision): distinguish ordinary wakes from repair * no-mistakes(review): Make passive guard follow-ups recovery-only * no-mistakes(document): Clarify recovery-only turn-end guard documentation
* fix(x-mode): dedupe pending mention wakes * no-mistakes(review): fix x-poll claim error deduplication * no-mistakes(review): separate claim diagnostics from relay recovery * no-mistakes(document): Document X-mode once-only mention wakes
…enguid#747) * feat(wake): enrich drained signal context * no-mistakes(review): Bound wake enrichment reads * no-mistakes(document): Document wake-drain annotations * docs(wake): explain at-least-once drain boundary * no-mistakes(review): Prevent symlink races in wake annotations * no-mistakes(review): Exercise wake symlink race regression * test: document intentional AFK marker subprocesses * fix(wake): isolate annotation marker state
When fm-send sends a message to a BUSY opencode crewmate on the tmux backend, opencode accepts the Enter and queues the message for the next turn, but leaves the typed text visible in the composer row. The submit-verification loop sees a pending composer, exhausts retries, and reports a false "Enter swallowed" failure while the message is actually delivered. Fix: after Enter retries are exhausted and the composer still shows pending, check fm_pane_is_busy. If the pane is busy (agent mid-turn, footer shows "esc interrupt"), the harness queued the message, so return "empty" (accepted). On an idle pane, keep returning "pending" (genuine swallow detection preserved). Regression tests cover four scenarios: - busy pane + pending composer -> empty (message queued) - idle pane + pending composer -> pending (genuine swallow) - busy pane + composer clears on first Enter -> empty - idle pane + composer clears on first Enter -> empty (existing path)
PR Summary by QodoFix tmux send false failures on busy opencode panes (queued Enter)
AI Description
Diagram
High-Level Assessment
Files changed (82)
|
|
Superseded by the no-mistakes pipeline PR. |
Code Review by Qodo
Context used✅ Compliance rules (platform):
25 rules 1. fm-watch-arm.sh prints extra stdout
|
| } | ||
|
|
||
| report_healthy() { | ||
| report_attached() { |
There was a problem hiding this comment.
1. fm-watch-arm.sh prints extra stdout 📘 Rule violation ≡ Correctness
bin/fm-watch-arm.sh emits status lines not allowed by the checklist (e.g., watcher: attached ... and alternate FAILED strings) and also prints additional non-status output to stdout. This violates the requirement that the script emit exactly one well‑formed status line on stdout with the approved prefixes.
Agent Prompt
## Issue description
`bin/fm-watch-arm.sh` must print exactly one approved status line to stdout, but it currently prints `watcher: attached ...`, alternative FAILED lines, and additional watcher output.
## Issue Context
Compliance requires stdout to contain exactly one status line starting with one of: `watcher: started `, `watcher: healthy `, or `watcher: FAILED - no live watcher with a fresh beacon`. Any additional output should be routed to stderr or suppressed.
## Fix Focus Areas
- bin/fm-watch-arm.sh[229-252]
- bin/fm-watch-arm.sh[425-487]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| __pycache__/ | ||
| *.pyc | ||
| .env | ||
| config/crew-harness |
There was a problem hiding this comment.
2. Missing config/ in .gitignore 📘 Rule violation § Compliance
Root .gitignore does not include an active config/ directory ignore entry; it only ignores specific config/... files. This violates the requirement that config/ (and other required directories) be ignored as directories in the root .gitignore.
Agent Prompt
## Issue description
The compliance checklist requires `config/` (directory) to be ignored in the root `.gitignore`, but the file currently contains only specific `config/...` entries.
## Issue Context
Ignoring only individual files can allow new config files to be accidentally committed.
## Fix Focus Areas
- .gitignore[1-18]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # herdr-sm-spaces-k4). Both branches converge on the same $T ("target") string | ||
| # that every downstream operation (send/capture/kill) already treats as opaque | ||
| # per-backend routing (fm_backend_resolve_selector). | ||
| validate_spawn_worktree() { # <source> <inspect-target> |
There was a problem hiding this comment.
3. validate_spawn_worktree lacks git-dir check 📘 Rule violation ≡ Correctness
bin/fm-spawn.sh introduces validate_spawn_worktree() but does not implement the required comparison of git ... rev-parse --absolute-git-dir between the candidate worktree and the primary checkout. This weakens the mandated isolation validation and does not meet the checklist’s explicit verification steps.
Agent Prompt
## Issue description
`bin/fm-spawn.sh`'s isolation check should verify the candidate worktree is distinct from the primary checkout by comparing `git -C <path> rev-parse --absolute-git-dir` values (and abort if they match). The current check uses `--show-toplevel` and path comparisons only.
## Issue Context
The compliance checklist explicitly requires git-dir comparison to reliably detect when the resolved path is actually the primary checkout.
## Fix Focus Areas
- bin/fm-spawn.sh[675-691]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| [ "$READ_ONLY" -eq 1 ] || fm_guard_clear_stale_banner | ||
| fi | ||
|
|
||
| # Queued wakes are an independent hazard; warn whenever they are pending, even if |
There was a problem hiding this comment.
4. fm-guard.sh missing combined wake warning 📘 Rule violation ☼ Reliability
bin/fm-guard.sh warns about queued wakes independently, but it does not emit a specific warning when both (a) tasks are in flight and (b) queued wakes are pending. This violates the requirement for an explicit combined-condition warning.
Agent Prompt
## Issue description
When there are tasks in flight and `state/.wake-queue` has pending entries, `bin/fm-guard.sh` must emit a dedicated warning mentioning both conditions.
## Issue Context
Current logic prints a queued-wakes warning without checking/mentioning in-flight tasks, so operators don’t get the required combined-state signal.
## Fix Focus Areas
- bin/fm-guard.sh[186-218]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| printf 'empty'; return 0 | ||
| fi | ||
| # Strip a leading prompt glyph, then re-judge the remainder. | ||
| case "$content" in |
There was a problem hiding this comment.
5. Glyph trim locale bug 🐞 Bug ≡ Correctness
fm_composer_classify_content strips a leading prompt glyph+space using content=${content#??},
which is byte-based under LC_ALL=C and can corrupt multibyte glyph prefixes (❯/›). When that
happens, the post-strip idle-placeholder recheck can fail and the function can incorrectly return
pending instead of empty.
Agent Prompt
### Issue description
`fm_composer_classify_content` removes leading glyphs with `${content#??}` / `${content#?}`. In byte-oriented locales (e.g. `LC_ALL=C`), `??` removes two *bytes* (not two characters), which breaks multibyte prompt glyphs like `❯`/`›` and can prevent the second idle-placeholder match from working, producing the wrong verdict.
### Issue Context
This function is a shared owner used by multiple backends, so a locale-dependent misclassification can affect send/inject safety and submit verification.
### Fix Focus Areas
- bin/fm-composer-lib.sh[209-219]
### Suggested fix
Replace the fixed-width removals with explicit prefix removals per glyph (e.g. `content=${content#'❯ '}` / `content=${content#'❯'}` etc.), or compute the matched prefix and strip that exact prefix, avoiding byte-count assumptions.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| done | ||
| # Retries exhausted, composer still shows pending. |
There was a problem hiding this comment.
6. Busy regex false-success risk 🐞 Bug ≡ Correctness
After Enter retries are exhausted, fm_tmux_submit_enter_core now returns empty when fm_pane_is_busy matches, even if the composer is still pending. Because fm_pane_is_busy greps for unanchored substrings like Working... in recent pane output, ordinary conversation text containing those substrings can cause a real swallow to be reported as a successful submit.
Agent Prompt
### Issue description
`fm_tmux_submit_enter_core` now treats a still-`pending` composer as submitted when `fm_pane_is_busy` returns true. `fm_pane_is_busy` currently matches a broad regex against the last non-blank lines of the pane; if the conversation tail contains a matching phrase (e.g. literal "Working..."), the function may return `empty` even though Enter was genuinely swallowed and the message was not queued.
### Issue Context
This is a new risk because the busy heuristic is now used as a *submit success* signal, not just an observational busy indicator.
### Fix Focus Areas
- bin/fm-tmux-lib.sh[49-53]
- bin/fm-tmux-lib.sh[127-134]
- bin/fm-tmux-lib.sh[145-165]
### Suggested fix
Make the busy check stricter for the queued-submit reclassification, e.g.:
- Anchor the regex to the full footer line (or detect the footer in a known screen region).
- Prefer matching only the last line (or last N lines with stronger structure) rather than any substring in the tail.
- Optionally introduce a dedicated `fm_pane_footer_is_busy` used only for this submit fallback.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Root cause
fm_tmux_submit_enter_core(inbin/fm-tmux-lib.sh) types text, pressesEnter, then verifies delivery by reading the composer row. When the target
opencode pane is mid-turn (busy, footer shows
esc interrupt), opencodeaccepts the Enter and queues the message for processing when the current
turn ends, but leaves the typed text visible in the composer row. The
verification loop sees a
pendingcomposer, exhausts all retries, andreports the submit as failed with
error: text not submitted ... (Enter swallowed; text left in composer)and exits 1 — even though the messagewas delivered and will be consumed.
This is a false failure: callers that retry on non-zero exit send the
message twice.
Fix
After the Enter retries are exhausted and the composer still shows
pending, check whether the pane is BUSY using the existingfm_pane_is_busy(which scans the pane tail for the busy footer regexesc (to )?interrupt|Working...|Ctrl+c:cancel). If the pane is busy,the harness has queued the message, so return the accepted verdict
(
empty) instead ofpending. On an idle pane, keep returningpending— a genuine swallow must still be detected.This uses a robust, version-independent signal (the busy footer) instead
of a brittle label match. Retries still run first (a transient render
clears on retry); only the post-retries-verdict is reclassified.
Empirical evidence
opencode version: 1.18.4
Before (busy pane)
Send to a pane showing
esc interrupt:Message IS visible in the conversation with
QUEUEDlabel and isconsumed when the turn ends.
After (busy pane)
The same scenario now exits 0. Message is delivered exactly once.
Idle genuine-swallow unchanged
An idle pane where Enter does not submit still reports
pendingandexits 1. The fix does not mask real swallows.
Regression tests
tests/fm-tmux-submit-busy.test.shcovers four scenarios:emptypendingemptyemptyAll mock the underlying tmux commands and override
fm_pane_is_busywith a test stub (controlled by
FM_FAKE_PANE_BUSY).Compatibility notes
herdr backend
fm_backend_herdr_send_text_submithas a parallel gap: when thebaseline agent status is
busy(pre-Enter agent wasworkingorblocked), it falls back tofm_backend_herdr_composer_stateforsubmit confirmation. An opencode pane that is busy and queues the
message would also return
pendingafter retries. A busy-state checkanalogous to the tmux fix would need to consult
fm_backend_herdr_busy_state, but the herdr case is more nuancedbecause a
blockedagent (stuck waiting on human input) should notauto-accept. Needs separate investigation.
orca and cmux backends
Both use the same composer-state verification pattern (
empty/pendingreturned by
fm_backend_orca_composer_state/fm_backend_cmux_composer_state) but have nois_busyprimitive. Notapplicable without one.
zellij backend
Uses content-diff (capture before/after Enter) instead of composer
state. If opencode queues a message without changing the pane content,
the diff shows no change — same potential gap, but there is no
is_busyprimitive. Not applicable without one.