feat(bin): add fork-sync, firstmate-update, and AXI-suite checks#4
Merged
Conversation
* fix: ignore secondmate home marker during sync (kunchenguid#417) * fix: gitignore the secondmate home marker bin/fm-home-seed.sh writes an untracked .fm-secondmate-home marker into every seeded secondmate home. A secondmate home is a worktree of the firstmate repo, so any plain `git status --porcelain` dirtiness check counted the untracked marker and the home read as dirty forever: fleet-sync reported it STUCK and the local fast-forward convergence sweeps risked leaving it stale on firstmate updates. Add .fm-secondmate-home to the tracked .gitignore so the marker is invisible to every dirtiness check uniformly, without weakening fleet-sync's deliberate untracked-counting for project clones. Convergence chicken-and-egg: existing homes predate the fix and it only arrives by fast-forward. The already-present marker-tolerant ff-skip (ignore_seed_marker=yes, used by the bootstrap sweep, /updatefirstmate, and spawn pre-launch) advances such a home past the fix commit, after which .gitignore takes over - no hand intervention. Tests in tests/fm-secondmate-sync.test.sh cover a freshly seeded home reading clean, an existing marker-only home converging then reading clean, and a genuinely dirty home still skipping. * no-mistakes(review): Captain: document standalone-clone update path * no-mistakes(document): Document secondmate marker migration * fix(composer): prevent dead-shell message injection (kunchenguid#416) * fix(composer): stop reading dead-shell prompts as empty agent composers Consolidate composer empty/pending/unknown classification into one shared owner, bin/fm-composer-lib.sh's fm_composer_classify_content, delegated to by all four backend adapters (tmux via fm-tmux-lib.sh, herdr, orca, cmux). This replaces four drifting copies of the glyph decision. Safety fix: a bare shell prompt glyph (> $ % #) on an unstructured row is now classified unknown (a dead shell, unsafe for injection), not empty. It is only empty inside a bordered composer box (the harness's own prompt). Agent glyphs ❯ (claude) and › (codex) read empty either way. The away-mode injector (inject_msg) now requires an affirmatively-empty composer, deferring on pending or unknown, so an escalation can never be typed into (or executed by) a pane whose agent exited to its login shell. Regression coverage: new tests/fm-composer-lib.test.sh pins the shared owner; per-backend dead-shell tests in fm-daemon (tmux + injector), orca, and the existing herdr/cmux suites. shellcheck clean; herdr incident regressions stay green. * no-mistakes(review): Captain: harden composer safety checks * no-mistakes(test): Stabilize Herdr prune safety setup * no-mistakes(document): Document composer injection safety * no-mistakes(lint): Clean composer safety lint * no-mistakes: apply CI fixes * feat(watcher): add paused external-wait supervision (kunchenguid#421) * feat(watcher): add paused/awaiting-external crew state A crew (or firstmate steering it) can declare a deliberate wait on a known external dependency with a paused: <reason> status. Both the always-on watcher and the away-mode daemon absorb such an idle pane through shared fm-classify-lib.sh vocabulary instead of tripping the possible-wedge stale escalation, and re-surface it for a recheck only on a long bounded cadence (FM_PAUSE_RESURFACE_SECS) so a forgotten pause cannot rot invisibly. fm-crew-state.sh reports state: paused distinctly. A crew that goes idle without declaring a pause classifies exactly as before. Docs and brief scaffold state lists updated; tests colocated. * no-mistakes(review): Captain: fix paused-state transitions * init * no-mistakes(review): Captain: fix paused-state supervision transitions * no-mistakes(review): Captain: fix paused supervision handoffs * no-mistakes(review): Reconcile paused supervision markers * no-mistakes(review): Captain: prioritize paused states over captain relevance * no-mistakes(review): Captain: preserve paused-working wedge timer * no-mistakes(review): Captain: honor configured pause verb in briefs * no-mistakes(test): Captain: fix AFK paused watcher handoff * no-mistakes(document): Document declared external waits * no-mistakes(lint): Clean paused-state lint --------- Co-authored-by: fmtest <fmtest@example.invalid> * fix: preserve X-mode follow-up platform limits (kunchenguid#425) * fix(x-mode): make follow-up platform splitting immune to link ordering A ~470-char Discord follow-up posted as a (1/2)(2/2) thread split at ~280 chars because fm-x-link only learned the platform from the inbox payload, and the fmx-respond ack path can drain that inbox file before the task is linked. A link recorded after cleanup silently lost the platform and the splitter defaulted to the X 280-char budget. Make platform resolution ordering-proof: - fm-x-link now resolves the platform AUTHORITATIVELY by request_id via a new fmx_request_relay_context helper (POST /connector/request-context) when neither the inbox payload nor carry flags carry it. The request_id survives the inbox drain, so a post-cleanup link still learns the right split budget. Best-effort: no token/curl or a non-2xx relay degrades to the loud warning below rather than a silent X default. - fm-x-link warns loudly when no platform source resolves, so the loss is never silent. - The fmx-respond procedure now orders link-before-inbox-cleanup so the fast local path stays correct without a relay round-trip. Colocated regression tests: a Discord follow-up >280 <2000 posts as ONE message even when linked after inbox cleanup, and an unresolvable platform warns loudly instead of splitting silently. docs/configuration.md documents the request-context lookup. The relay endpoint is the companion durable change (see done status); until it ships, the link-before-cleanup reorder keeps the normal path correct. * no-mistakes(document): Document X-mode platform recovery * fix(composer): handle ANSI ghost text safely (kunchenguid#429) * fix(composer): one ANSI-aware ghost owner covers claude dim + grok truecolor Away-mode injection wedged all night on the primary claude-on-herdr pane: the herdr composer classifier never stripped generic dim ghost text (only a narrow codex bold-wrapped byte-pattern check), so claude's rotating prompt-suggestion ghost - a bare "❯" then SGR-2 dim text, which herdr's ANSI pane read preserves - read as real pending input and every escalation deferred (6524 lifetime "pending input (non-empty composer)" defers; wedge 30623s). Consolidate ghost extraction into one fleet-wide ANSI-aware owner, fm_composer_strip_ghost (bin/fm-composer-lib.sh), that drops every de-emphasised run - dim/faint (SGR 2: claude, codex) AND a dark/muted truecolor foreground (grok's placeholder, luminance below FM_COMPOSER_GHOST_LUMA_MAX, default 128, dark-theme assumption). Both ANSI-capable backends route through it: fm_tmux_composer_state (fm_tmux_strip_ghost is now a thin adapter) and fm_backend_herdr_composer_state. The herdr-only faint byte-pattern check is removed and fm_backend_herdr_strip_ansi reduced to a thin adapter over the shared fm_composer_strip_ansi. Bordered detection now reads the plain row so a dark box border dropped with the ghost does not lose the composer shape. This also closes the documented grok TRUECOLOR placeholder gap by the same mechanism (harness-adapters skill note updated). Empirical evidence (read-only live capture + isolated tmux, no herdr lifecycle) and the incident write-up are in docs/herdr-backend.md; deterministic regressions feed the exact captured bytes through the real classifiers (tests/fm-backend-herdr.test.sh, tests/fm-composer-ghost.test.sh). Two prior ghost-test fixtures that used a near-black 38;2;1;2;3 as "real" colored text (never a realistic real-input color) are corrected to a bright 38;2;224;222;244, preserving the truecolor payload-skip parser intent. * no-mistakes(review): Preserve dark shell prompt safety * no-mistakes(review): Harden erased shell prompt classification * no-mistakes(document): Document shared composer ghost extraction * no-mistakes(lint): Normalize tmux comment punctuation * fix(spawn): make tmux window handling robust under non-default config (kunchenguid#134) * test: isolate session-start suite from ambient harness markers (kunchenguid#432) * fix(session-start): isolate harness env markers in suite runner Neutralize CLAUDECODE, PI_CODING_AGENT, and GROK_AGENT in run_session_start so ambient interactive shells cannot override the suite's fake ps harness (local-vs-CI split on the pi supervision case). * no-mistakes(document): Correct Pi marker documentation * fix(teardown): retry transient index locks during worktree return (kunchenguid#435) * fix(teardown): retry treehouse return on transient index.lock Killed crew git ops can leave a short-lived worktree index.lock that makes treehouse return fail. Retry on that error signature with a bounded wait (env-overridable), never force-delete a live lock, and only then fall back to the existing provably-stale cleanup path. * no-mistakes(review): Harden teardown retry configuration * no-mistakes(document): Document teardown index-lock retry behavior * no-mistakes(lint): Fix empty shell variable assignments * fix: complete brief help and consolidate documentation (kunchenguid#438) * docs: de-feature the scripts.md and CONTRIBUTING test inventories Slice 1 of the documentation redundancy cleanup wave (firstmate scope). docs/scripts.md: every row is now one purpose clause; script headers are the declared owner of behavior, flags, and contracts. Coverage stays 61/61 scripts; bytes drop 19,922 -> 7,958. CONTRIBUTING.md: the 54-row per-test inventory is gone; contributors discover tests by listing tests/*.test.sh and reading each script's own header, and gated tests print their own skip gates. The run commands, symlink assertions, and watcher smoke line are unchanged. Lines drop 135 -> 84 (18,797 -> 7,831 bytes). Two facts that existed only as inventory rows moved into their owners' headers first: fm-brief.sh's paused-vs-blocked scaffold distinction and fm-session-start.sh's Pi extension-loaded check. No instruction-surface or behavior change; AGENTS.md untouched. * no-mistakes(review): Captain, fix brief help and Grok test discovery * no-mistakes(review): Captain: document Grok lock-holder test coverage * fix: detect Git and centralize backend configuration (kunchenguid#445) * docs: consolidate universal backend contracts into configuration.md Slice 2 of the documentation redundancy cleanup wave (firstmate scope). docs/configuration.md is now the declared single owner of three universal contracts, each with an explicit ownership sentence: - the universal toolchain list (Toolchain), now also carrying the per-tool purpose clauses that previously lived only in the tmux guide; - the task-selector vocabulary (Runtime backend); - the tasks-axi compatibility definition (Backlog backend). The five backend guides' prerequisites replace their verbatim universal-requirements parentheticals (5 full copies) with a pointer plus only backend-specific items; zellij/cmux selector restatements and architecture.md's partial copy become pointers or are dropped; CONTRIBUTING's compatibility sentence becomes a pointer; two near-verbatim orca-bootstrap restatements (configuration.md Runtime backend, orca guide) collapse into the Toolchain owner copy. Backend-specific setup, behavior, target-string shapes, and every empirical verification record are untouched. AGENTS.md untouched (slice 3). * docs: include git and GitHub auth in the toolchain owner list The review flagged that the new universal-toolchain owner omitted git and GitHub authentication while every backend guide now defers its prerequisites here; bootstrap's NEEDS_GH_AUTH check makes them real universal requirements. * no-mistakes(review): Detect Git in bootstrap toolchain * no-mistakes(document): Clarify GitHub CLI and centralize selector documentation * feat(daemon): add backend-independent wedge alerts (kunchenguid#444) * feat(daemon): backend-independent active alert for the wedge alarm When away-mode injection wedges past max-defer, inject_wedge_alarm only actively signalled via the tmux status-line, which is skipped on non-tmux backends. A wedged claude-on-herdr primary left only the passive state/.subsuper-inject-wedged marker (2026-07-10 overnight incident). Add a config-gated active alert (config/wedge-alarm, local/gitignored; FM_WEDGE_ALARM_CHANNEL) that reaches the captain even when every pane and its status-line is unreadable: an OS-level macOS notification (osascript), a herdr notification, or a captain-supplied command. Default-on (auto) so the alarm is never silent; each channel best-effort, degrading to the next and never crashing the daemon loop. The tmux flash and durable marker stay. The OS notifiers route through a single FM_WEDGE_ALARM_EXEC seam. When the daemon is sourced (only tests do this; production execs it) the seam defaults to "discard", and tests/wake-helpers.sh points it at a recorder, so it is structurally impossible for any test to post a real notification. Channels verified once manually on macOS 26.5.2 / herdr 0.7.3; see docs/wedge-alarm.md. * no-mistakes(review): Bound wedge alarm notifier execution * no-mistakes(review): Captain: harden wedge alarm notifier safety * no-mistakes(review): Captain: harden wedge alarm test notifier isolation * no-mistakes(review): Captain: harden wedge alarm throttling * no-mistakes(review): Redact wedge alarm directive logs * no-mistakes(review): Harden wedge alarm notifier safety * no-mistakes(review): Track notifier process groups through cleanup * no-mistakes(document): Document wedge-alarm active alert behavior * docs: centralize firstmate operating contracts (kunchenguid#447) * docs(agents): extract conditional AGENTS.md material to owned homes Slice 3 of the documentation redundancy cleanup wave (firstmate scope): the always-loaded instruction surface drops from 941 lines / 116,733 bytes (~29k tokens per session per fleet member) to 785 / 91,353 (~22.8k tokens), moving only audit-identified conditional and situational material while preserving every load-bearing invariant at its trigger point via the inline-stub pattern. Moves, each to one declared owner plus an inline stub: - section 3's bootstrap output-line handbook (~44 lines) -> new agent-only bootstrap-diagnostics skill, added to the section 13 trigger index; the detect-consent-install rule and the do-not-dispatch gate stay inline as safety-critical. - section 4's crew-dispatch JSON schema and field semantics -> docs/configuration.md 'Crew dispatch profiles' (pointer direction flipped); the intake procedure, precedence, backstop, and never-select-unverified rules stay inline. - section 4's quota-balanced algorithm -> bin/fm-dispatch-select.sh header (now the declared owner; usage() converted to the dynamic header extraction pattern PR kunchenguid#438 established for fm-brief.sh). - section 7's spawn resolution narrative and example sprawl -> bin/fm-spawn.sh header; the isolated-worktree assertion, refusal-is- a-blocker rule, and post-spawn duties stay inline. - section 7's teardown landed-work mechanics -> bin/fm-teardown.sh header (section 1's containment pointer retargeted); the fork benign case and never-force rule stay inline. - section 8's watcher classification narrative -> docs/architecture.md 'Event-driven supervision' (already the owner); every operative rule (one live cycle, no turn ends blind, drain first, wake ladder, never-pkill, guard responses) stays inline. - sections 3/4/6/7 secondmate sync, propagation, schema, and handoff restatements -> secondmate-provisioning skill, now the declared owner including the literal-file inheritance nuance. - section 14's X-mode cadence mechanism -> docs/configuration.md 'X mode (.env)', closing issue kunchenguid#363; activation semantics, the fmx-respond trigger, and the terminal-wake final-follow-up duty stay inline. CLAUDE.md stays a symlink; no behavior or test change. * no-mistakes(document): Centralize contract-owner documentation * fix(cmux): close last workspace during teardown (kunchenguid#449) * fix(cmux): close the last/selected workspace in a window at teardown cmux keeps every window at >=1 workspace, so close-workspace on the only workspace in a window silently no-ops (returns OK, workspace stays), and a window holding a live session cannot be closed over the control socket. That left a selected task workspace open at teardown (the last workspace in a window is always the selected one). Add fm_backend_cmux_window_of_workspace and have fm_backend_cmux_kill create a throwaway default sibling in the target's window before closing when the target is the last workspace there, so the close lands; the window keeps a fresh default workspace (cmux's own "closed the last tab" outcome). Non-last teardown closes directly, as before. Cover both kill branches plus the helper with fake-CLI unit tests, add a real-cmux window/count detection smoke assertion, and record the empirical evidence in docs/cmux-backend.md. * no-mistakes(review): Derive cmux count from membership snapshot * no-mistakes(document): Document cmux last-workspace teardown behavior * fix: recover orphaned packed-refs locks during fleet sync (kunchenguid#453) * fix(fleet-sync): recover from an orphaned packed-refs.lock A git ref rewrite (fetch --prune, pack-refs, branch -D) killed after creating .git/packed-refs.lock but before renaming it - e.g. bootstrap's timed-out fleet-sync kill or teardown's process kills - leaves a lock that makes the next sync's fetch fail with "Unable to create '...packed-refs.lock': File exists", leaving the clone unsynced. On that signature only, fm-fleet-sync.sh now retries the fetch with a bounded wait (transient locks self-clear), then removes the lock and retries once more ONLY when it is provably stale: still present, mtime age past a threshold, and no lsof holder of the lock file or of the clone worktree itself (a live git keeps that as its cwd even in the window after it closes the lock and before it exits). A live lock, a missing lsof, any failed check, or any other fetch failure keeps today's behavior. Every wait/retry/removal prints to stderr, and a successful recovery also prints one "recovered:" summary to stdout so a session-start refresh - which discards fleet-sync stderr and relays only stdout - still surfaces it. The shared "is this git lock provably abandoned?" proof is extracted into bin/fm-lock-lib.sh so it has one owner, used by both fm-teardown.sh and fm-fleet-sync.sh. Constants are env-overridable knobs. tests/fm-gotmp.test.sh gains the fm-lock-lib.sh symlink teardown now needs in its fake bin/. * no-mistakes(review): Captain, remove obsolete teardown wake dependency * no-mistakes(document): Document packed-refs lock recovery architecture * feat(herdr): escalate blocked panes immediately (kunchenguid#472) * feat(herdr): immediate blocked-state escalation via native events.subscribe push Fold herdr's native pane.agent_status_changed stream into the single watcher so a crew entering blocked wakes its supervisor sub-second (measured 0.129s) instead of after the ~240s stale-pane wedge timer. - bin/fm-transition-lib.sh: backend-neutral normalized-transition record shape plus the single-owner status->action policy table (blocked=actionable, working=absorb+clear-dedupe, idle/done=defer, else=fall back to polling). - bin/backends/herdr.sh + herdr-eventwait.py: a raw AF_UNIX events.subscribe subscriber over one connection for all this home's herdr panes, subscribing to ALL statuses, returning the first fresh blocked edge, with a per-pane dedupe marker and a reconnect level-reconcile. Version/schema capability gate. - bin/fm-backend.sh: has-push / events-capable / wait-transition dispatchers so the watcher stays backend-agnostic and the shape+policy are reusable. - bin/fm-watch.sh: splice the bounded event wait in as the watcher's terminal wait primitive (replacing the blind sleep POLL for push-capable homes), behind a source guard so the splice is unit-testable; secondmate/paused exemptions; map pane->window->task and enqueue a stale wake. No second watcher process; the single-cycle invariant and every guard/beacon/turn-end mechanism are unchanged. - Polling stays the permanent fail-closed backstop: below-capability, subscribe failure, and repeated runtime failures all degrade to sleep. - Tests: fake-CLI units (fm-transition-lib, wait/apply/dedupe/reconcile/ fallbacks in fm-backend-herdr, watcher exemptions in fm-supervision-events) plus an isolated real-herdr idle->blocked smoke. docs/herdr-backend.md carries the dated evidence and retires the old gap note. * no-mistakes(review): Captain, fix Herdr disconnect handling and dedupe docs * no-mistakes(review): Captain, commit markers after wake and reuse capability cache * no-mistakes(review): Captain, clear stale markers and secure Herdr FIFOs * no-mistakes(review): Captain, subscribe before Herdr reconciliation * no-mistakes(review): Captain, make Herdr FIFO handling Bash 3.2-safe * no-mistakes(test): Captain: include lock library in teardown fixture * no-mistakes(document): Captain: document Herdr immediate blocked escalation * fix: clarify shellcheck conditionals * docs(readme): reposition firstmate as an agent distro (kunchenguid#473) * feat: add deterministic bounded bearings snapshots (kunchenguid#475) * feat(bearings): deterministic bearings snapshot + durable decision model Add bin/fm-bearings-snapshot.sh: a bounded TOON-by-default projection over the canonical fm-fleet-snapshot. Default is local-only (zero network); live open-PR discovery and checks happen only under --include-prs, which fails soft. Every dropped surface is marked in omitted[] with the flag that reveals it, and the prs: line states when checks were not requested, so absence is never silent. Fix the unresolved-decision masking bug in the canonical layer. fm-classify-lib gains status_open_decisions, the one authoritative keyed open/resolved fold over the whole status stream: needs-decision/blocked opens a keyed entry, only an explicit keyed resolution (or, for run-backed tasks, run-step advancement) closes it, so a later unrelated done/paused can no longer mask a still-open captain decision. fm-fleet-snapshot surfaces hints.open_decisions and derives pending_decision/blocked_event from it; the canonical schema stays complete. Point the /bearings skill at the one command; add the resolved: writer line to ship, scout, and secondmate briefs. Register the script and add regression tests for the output bound, TOON/JSON parity, local-only default, opt-in PR fetch, partial-failure degradation, decision durability, and report pointers. * fix(bearings): completed scout report is a pointer, not a pending decision A completed scout that raised a needs-decision and then finished (done) without a keyed resolution falsely surfaced as an open/pending decision (the Lavish-103 case). Root cause: the open-decision reconciliation in bin/fm-fleet-snapshot.sh cleared a stale decision only for a live run-step/pane activity read, so a terminal task whose current state is read from the status log (a scout or ship that reached done/failed) never cleared its stale, never-keyed-resolved needs-decision, and it lingered as pending. The open-decision set is still derived purely from the keyed fold - never from a report body or decision-like prose - and reconciled against the crew lifecycle. Extend that reconciliation so a terminal done/failed state on a single-owner task (scout or ship), whose deliverable is its report or PR, also clears the set; a completed scout now surfaces only as a report pointer. Secondmates are excluded from the terminal clear (persistent, multiplexed stream), which keeps the unrelated-event masking fix intact. Add regression tests: a completed scout with decision-like report prose is a pointer not pending (canonical + end-to-end), and a scout still parked at a decision stays pending so the terminal clear never over-fires. * no-mistakes(review): Captain, preserve keyed decisions across shared status parsing * no-mistakes(review): Captain, close blockers and harden keyed decision parsing * no-mistakes(review): Captain, bound GitHub enrichment without coreutils timeout * no-mistakes(review): Captain, bound bearings sections and fail closed * no-mistakes(review): Captain, disclose capped per-repository PR results * no-mistakes(document): Refresh bearings documentation and status contracts * fix(bearings): avoid ambiguous worktree guard * fix: enforce deterministic ShellCheck parity (kunchenguid#481) * fix(lint): one shellcheck owner pinned to 0.11.0 for CI/local parity Firstmate PRs passed local no-mistakes validation but failed CI's "Lint shell scripts" job on shellcheck findings (SC2015, SC1007, SC2034). Two divergences caused it: 1. The no-mistakes gate had no commands.lint, so its lint step never ran the deterministic shellcheck bin/*.sh bin/backends/*.sh tests/*.sh that CI runs. Confirmed from state.sqlite: the lint step_result recorded findings:null with no lint agent invocation. 2. CI's shellcheck floated with the runner image while local ran a newer build; shellcheck retired SC2015 in 0.11.0, so an older CI shellcheck rejected an SC2015 that the newer local one no longer emits. Establish bin/fm-lint.sh as the single owner of the lint definition: the file set, the config, and the pinned shellcheck version (0.11.0, printed via --required-version). Both CI (.github/workflows/ci.yml) and the no-mistakes gate (.no-mistakes.yaml commands.lint) invoke it; CI installs the exact version it names and logs the resolved version, and fm-lint.sh refuses to lint under any other version. This is not a CI relaxation: it adopts shellcheck 0.11.0's rule set consistently, dropping only the upstream-retired, false-positive-prone SC2015; default severity and every still-supported finding stay enforced (no severity downgrade, no excludes). tests/fm-lint.test.sh asserts both gates invoke the owner, that CI installs and logs the pinned version, that the owner refuses a non-pinned shellcheck, and that it rejects a real lint defect the old no-op gate passed. * no-mistakes(review): Captain, harden deterministic ShellCheck parity * no-mistakes(review): Captain, neutralize ambient ShellCheck overrides * feat: guard primary shells from persistent cd commands (kunchenguid#483) * feat: add cd-guard PreToolUse seatbelt for the primary shell A stray persistent top-level `cd projects/<clone>` in the primary firstmate shell relocates the shell, so a later firstmate-owned command (a backlog write, an fm-* lifecycle call, tasks-axi) runs inside a project clone instead of the home. The cd-guard denies exactly that command shape before it runs, across all five verified primary harnesses, mirroring the watcher-arm PreToolUse seatbelt. - bin/fm-cd-command-policy.mjs: sole block/allow decision owner. Reuses the shell classifier exported from bin/fm-arm-command-policy.mjs (no duplicate lexer; that file's CLI now runs only when invoked directly). - bin/fm-cd-pretool-check.sh: transport, strict-superset prefilter, harness output rendering, and primary-checkout scoping - fires in a secondmate's own primary session, inert in crew/scout child worktrees and non-firstmate repos. - Wired into claude, codex, grok, opencode, and pi PreToolUse-equivalents; per-harness hooks only call the owner. - Blocks top-level cd/pushd/popd (including cd to an absolute path, X=1 cd, and command cd). Allows git -C, subshell / bash -c / env -C / make -C / find -execdir, pipeline and background forms, and cd-as-data. Fails open on malformed input; agent-mistake threat model. - tests/fm-cd-pretool-check.test.sh: 43-case x 5-harness-entry-form matrix, end-to-end cwd-leak regression, scoping, fail-open, prefilter, and wiring. - docs/cd-guard.md: full contract plus live validation (claude, codex, opencode, pi blocked end-to-end; grok live run blocked by an API balance limit, with mechanism parity and deterministic coverage recorded). * no-mistakes(review): Captain, fix cd-guard classification and prefilter coverage * no-mistakes(review): Captain, allow path-qualified command wrappers * no-mistakes(review): Captain, allow non-executing command queries * no-mistakes(test): Captain, clarify cd-guard safe-path remediation * docs: clarify cd guard guidance * no-mistakes(document): Clarify cd-guard safe target guidance * brief: add no-mistakes shared-daemon rule to ship and scout scaffolds (kunchenguid#267) Crews must never stop, restart, or update the shared no-mistakes daemon since one instance serves every firstmate lane/home; a restart kills other lanes' in-flight pipeline runs and forces expensive re-runs. Encodes this as a new numbered rule in both the ship-task and scout-task brief scaffolds. Co-authored-by: mielyemitchell <249051873+mielyemitchell@users.noreply.github.com> * feat: make bearings concise and accurate (kunchenguid#485) * feat(bearings): four-section chat contract, accurate secondmate landed, resolved-event state render /bearings skill (one owner of the chat-response format): mandate the four always-present chat sections - Captain's Call, Recently Landed, Underway, Charted Next - each with an explicit empty-state sentence, no At Anchor, materially shorter than and linking to the report file. Resolves the ambiguous Check first / Decisions pending split into one strict captain-action section. fm-crew-state: the log fallback derives current state only from a real run-state verb, so a trailing decision-closing resolved: event no longer renders a healthy idle crew (typically a secondmate) as unknown with the resolution prose as its detail. The keyed-decision contract in fm-classify-lib.sh is untouched; map_log_state stays the one verb->state owner. fm-fleet-snapshot: add a bounded, read-only secondmate_landed roll-up of Done records from registered secondmate homes, reusing the single backlog parser and the one secondmate-home enumerator (meta home= with data/secondmates.md fallback); no network, per-home capped. fm-bearings-snapshot: landed now merges main-home Done with the secondmate roll-up, bounded by a per-home cap and an overall cap with omitted[] disclosure (also fixing the previously-silent landed truncation); --all-landed reveals the full set. tests: resolved-event state render, secondmate landed aggregation with caps and omitted[] disclosure, Captain's Call anti-leak, and the four-section contract. * no-mistakes(review): Captain, ensure bearings reveals all landed work * no-mistakes(document): Document bearings accuracy contracts * add AXI suite self-update checks * no-mistakes(review): {"summary": "fix AXI-suite stuck-signal self-clear, hook hints, and stale replay"} * no-mistakes(test): test: skip node-only Pi extension tests, use tasks-axi for MISSING diagnostic * no-mistakes(review): Gate AXI-suite hook retry behind check-only, add regression tests * no-mistakes(review): Fix bounded() no-timeout-binary hang and sort -V version compare * no-mistakes(document): Add fm-axi-suite.sh doc coverage: scripts index, state files, env vars * no-mistakes(review): Bound AXI-suite registry checks to a single cumulative timeout budget * no-mistakes(test): Skip shellcheck-clean check in cd-guard test when ShellCheck unresolved * no-mistakes(document): Documented --check-only/--force flags and FM_AXI_SUITE_TOOLS in fm-axi-suite.sh header and docs/configuration.md * no-mistakes(review): Make --check-only fully non-mutating; align AGENTS.md doc * no-mistakes(document): Documented cd-guard/arm-pretool PreToolUse seatbelts and added missing scripts.md rows --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: fmtest <fmtest@example.invalid> Co-authored-by: Pierre Marais <pierremarais67@gmail.com> Co-authored-by: mielyemitchell <mielyemitchell@gmail.com> Co-authored-by: mielyemitchell <249051873+mielyemitchell@users.noreply.github.com>
* feat: wake on Bridge inbox traffic * no-mistakes(review): Bound Bridge inbox scan with watcher's CHECK_TIMEOUT to prevent hangs * no-mistakes(review): Cache Bridge inbox priority by cheap signature to skip redundant scans * no-mistakes(review): Gate Bridge inbox discovery scans to urgent-interval cadence * no-mistakes(review): Fix Bridge inbox cache signature to catch in-place envelope edits; document new cache files * no-mistakes(document): Document Bridge inbox watcher check in docs/architecture.md and docs/configuration.md --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
* Add upstream firstmate update check * no-mistakes(review): Document external cron/systemd scheduling for upstream update check * no-mistakes(document): docs(scripts,architecture,configuration): document upstream update check --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
…of stale local tree (#4) * Fix Bridge inbox checks against origin * no-mistakes(review): Fix word-splitting on Bridge inbox filenames in priority scan * no-mistakes(document): Sync AGENTS.md and fm-watch.sh comment with Bridge inbox origin/main fetch behavior --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
…fm/fork-sync-maintenance # Conflicts: # .agents/skills/bootstrap-diagnostics/SKILL.md # AGENTS.md # docs/architecture.md # docs/herdr-backend.md # docs/scripts.md # tests/fm-cd-pretool-check.test.sh
…alize docs cron path
… architecture.md and configuration.md systemd/env sections
feat: add curated fork sync detection and harden afk daemon lifecycle
* fix(watch): scope Bridge inbox vessel per home * no-mistakes(review): let empty FM_BRIDGE_VESSEL fall through to config file * no-mistakes(document): docs: sync remaining Bridge vessel opt-in references * no-mistakes(lint): suppress intentional SC2016 in bridge-inbox watcher test --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
…marker (#7) * Deduplicate Bridge inbox wakes * no-mistakes(review): clear Bridge surfaced marker on acked-empty inbox; guard marker write * no-mistakes(review): document bridge-surfaced marker in configuration.md state inventory --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
# Conflicts: # .agents/skills/bootstrap-diagnostics/SKILL.md # AGENTS.md # bin/fm-afk-launch.sh # bin/fm-bootstrap.sh # bin/fm-watch.sh # docs/architecture.md # docs/configuration.md # docs/scripts.md # tests/fm-cd-pretool-check.test.sh # tests/fm-pi-watch-extension.test.sh # tests/fm-session-start.test.sh # tests/fm-turnend-guard.test.sh
…-guard merge content
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Reconcile the repository's diverged histories into one branch that preserves both origin/main and all 12 already-completed commits from origin/fm/recover-unlanded-commits-jul19. The reconciliation must remain a genuine two-parent merge with the exact merge subject 'Merge unlanded local commits predating a later origin advance'; do not rebase, squash, force-push, or manually replace either parent history. Resolve overlapping shared instructions, scripts, skills, docs, and tests conservatively on merit, keeping additive functionality from both sides and preserving the newer safety and ownership contracts. Validate the complete merged tree, push the feature branch, and open a PR that Firstmate can later land with merge rather than squash.
What Changed
Merge unlanded local commits predating a later origin advance) landing 12 previously-unlanded fork commits onto origin/main, then restored merge-dropped AXI-suite/Bridge/docs/scripts.md/test-guard content and fixed an inverted Bridge-vessel config precedence description inAGENTS.md.bin/fm-axi-suite.sh(AXI-suite self-update and safety seatbelts),bin/fm-firstmate-update-check.sh(upstream firstmate update detection), andbin/fm-fork-sync-check.sh(curated fork sync detection), documented indocs/architecture.md,docs/configuration.md, anddocs/scripts.md.bin/fm-watch.shto wake on Bridge inbox traffic, scoped per home and deduplicated via a surfaced-signature marker, reading state from fetchedorigin/main; added matching coverage intests/fm-axi-suite.test.sh,tests/fm-firstmate-update-check.test.sh,tests/fm-fork-sync-check.test.sh,tests/fm-watch-bridge-inbox.test.sh, andtests/lib.sh.Risk Assessment
✅ Low: The single-line fix in e59d3a0 correctly corrects the previously-flagged Bridge vessel precedence description to match the actual code (bin/fm-watch.sh) and the cross-referenced docs/configuration.md, with no new issues introduced and a clean, marker-free working tree.
Testing
The configured baseline test command already passed; on top of that I verified via git plumbing that the merge is a genuine two-parent merge with the exact required subject and the exact 12 recovered commits from origin/fm/recover-unlanded-commits-jul19 layered onto origin/main, found zero leftover conflict markers, and ran targeted tests for every file the merge listed as conflicted (all pass, including a test that directly exercises the Bridge-vessel precedence bug the final commit fixed, cross-checked against the real code). The one intent requirement not yet satisfied at this commit is that the branch has not been pushed to origin and no PR has been opened.
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
.agents/skills/afk/SKILL.md- branch carries 12 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (148 file(s)) into the PR:Push main to origin, or rebase your branch onto origin/main, before gating.
🔧 **Review** - 5 issues found → auto-fixed (2) ✅
bin/fm-bootstrap.sh:805- The merge conflict resolution silently dropped functionality that the second parent (8a5fd11) added: the invocation of"$SCRIPT_DIR/fm-axi-suite.sh"inside the mutating block (was alongside secondmate_sync/x_mode_setup/fleet_sync at bin/fm-bootstrap.sh:805-810, now absent), and the four relay lines just beforeexit 0that surfacedstate/firstmate-update.available,.stuck,state/fork-sync.pending, and.stuck(verified viagrep -n "axi-suite\|firstmate-update\|fork-sync" bin/fm-bootstrap.sh→ zero matches in the script body). The header comment (lines 19-21) still documents AXI_SUITE_UPDATED/REVIEW/STUCK, FIRSTMATE_UPDATE_AVAILABLE/STUCK, and FORK_SYNC/FORK_SYNC_STUCK as things bootstrap prints, and docs/configuration.md:268 independently corroborates this was real intended wiring, not vestigial text. This contradicts the binding user intent's requirement to 'resolve overlapping shared instructions, scripts, skills, docs, and tests conservatively on merit, keeping additive functionality from both sides' — an entire feature (AXI-suite self-update via bootstrap, and surfacing of the firstmate-update/fork-sync diagnostics through bootstrap's detect output) was silently omitted rather than merged in.AGENTS.md:106- AGENTS.md — which the file itself calls 'your entire job description' — has zero mentions of the Bridge inbox feature after the merge (grep -i bridge AGENTS.mdreturns nothing), even though docs/architecture.md:47-48 and docs/configuration.md:368-372 fully document it and it's live, merged code in bin/fm-watch.sh. Specifically dropped from the second parent (8a5fd11): theconfig/bridge-vesselfile-layout entry (was near the config/ block, AGENTS.md:63-75 in the merged file has no such row though .gitignore was updated withconfig/bridge-vessel),.bridge-*in the 'watcher internals; never touch' glob at AGENTS.md:106 (currently lists.hash-* .count-* .stale-* ... .heartbeat-streakbut omits.bridge-*), an 'AXI-suite currency' subsection in section 3 covering the AXI_SUITE_STUCK escalation rule, and the 'Bridge inbox traffic' clause in section 8'scheck:wake-handling bullet (AGENTS.md:328 currently reads 'act on the named poll result, including merges and X-mode events' with no Bridge mention). This is dropped 'additive functionality' the intent requires be kept from both sides.docs/scripts.md:86- The canonical script index table in docs/scripts.md is missing rows for 8 scripts that exist on disk under bin/:fm-axi-suite.sh,fm-firstmate-update-check.sh,fm-fork-sync-check.sh(added by the second parent, 8a5fd11) andfm-lint.sh,fm-install-shellcheck.sh,fm-cd-pretool-check.sh,fm-cd-command-policy.mjs,backends/herdr-eventwait.py(added by the first parent, b384c1b). Both sides added rows to this same table and the conflict resolution kept neither side's additions for these 8 entries (onlyfm-transition-lib.shsurvived). This is dropped documentation the intent requires be preserved ('keeping additive functionality from both sides' explicitly includes 'docs').tests/fm-pi-watch-extension.test.sh:113- The second parent (8a5fd11) added afm_node_supports_ts_importhelper to tests/lib.sh plus guard calls at 6 call sites across tests/fm-pi-watch-extension.test.sh (lines ~113, 191, 329, 372) and tests/fm-turnend-guard.test.sh (lines ~796, 862) to skip Pi-extension tests that exec.tsplugin files vianode --input-type=moduleon Node runtimes lacking native TypeScript import support. The helper itself survived the merge into tests/lib.sh, but because both test files were resolved as pure first-parent (b384c1b) content, all 6 call sites were dropped —grep -rn fm_node_supports_ts_import .shows the helper is now defined but never invoked anywhere, i.e. dead code.tests/fm-cd-pretool-check.test.sh:437-test_scripts_are_shellcheck_cleanin tests/fm-cd-pretool-check.test.sh only checkscommand -v shellcheckbefore runningshellcheckwith no version pin or--norc. The second parent (8a5fd11) had fixed this to read the required version frombin/fm-lint.sh --required-version, skip with a clear message when the installed shellcheck doesn't match, and pass--norc— matching the conventionbin/fm-lint.shdocuments (REQUIRED_SHELLCHECK=0.11.0) and that tests/fm-lint.test.sh already follows. This fix was dropped when the file was resolved as pure first-parent content.🔧 Fix: Restore dropped AXI-suite/Bridge/scripts.md/test-guard merge content
1 warning still open:
AGENTS.md:75- The newly restoredconfig/bridge-vesselrow states: 'falls back to FM_BRIDGE_VESSEL, and absent disables Bridge inbox scans' — this describes config/bridge-vessel as primary and FM_BRIDGE_VESSEL as its fallback. That's backwards. bin/fm-watch.sh:110-115 checksFM_BRIDGE_VESSELfirst (if [ -n "${FM_BRIDGE_VESSEL:-}" ]; then ... elif [ -f "$FM_HOME/config/bridge-vessel" ]), and docs/configuration.md:370 (cross-referenced by this very line) already states it correctly: 'FM_BRIDGE_VESSEL selects the vessel and falls through to local config/bridge-vessel'. This exact sentence wasn't present in either original merge parent (8a5fd11's version of this line just said 'absent disables Bridge inbox scans' with no precedence claim) — the precedence wording is new text added by this fix commit itself, and it inverts the real resolution order right next to a correct cross-reference to the doc that says the opposite.🔧 Fix: Fix inverted Bridge vessel config precedence in AGENTS.md
✅ Re-checked - no issues remain.
git ls-remote origin fm/fm-recover-fork-driftreturns nothing andgh-axi pr listshows no PR for this branch (only an unrelated open PR feat: add bootstrap and watcher drift checks #3). The merge/tree content itself is fully validated and correct, but this required delivery step of the intent is not yet done.command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"git log -1 --format='%H %P %s' 6099e6a— confirmed two-parent merge, parents b384c1b and 8a5fd11c, exact subject 'Merge unlanded local commits predating a later origin advance'git rev-parse origin/fm/recover-unlanded-commits-jul19vs merge's second parent — exact matchgit log --oneline b384c1b..8a5fd11c— exactly the 12 expected commitsgit merge-base --is-ancestor b384c1b origin/main— base is first-parent ancestor of origin/maingrep -rn '^ |^ |^ 'across the tree — no leftover conflict markersbash tests/fm-cd-pretool-check.test.sh— pass, including restored shellcheck-required-version guardbash tests/fm-pi-watch-extension.test.sh— pass (some cases intentionally skip: Node lacks native .ts import support)bash tests/fm-session-start.test.sh— passbash tests/fm-turnend-guard.test.sh— passbash tests/fm-watch-bridge-inbox.test.sh— pass, including 'Bridge vessel resolution prefers a non-empty env value and falls back to per-home config'manual cross-check of AGENTS.md's corrected Bridge-vessel precedence text against bin/fm-watch.sh:110-113 logic — matchesmanual check that files referenced by the restored docs/scripts.md entries (fm-axi-suite.sh, fm-firstmate-update-check.sh, fm-fork-sync-check.sh, fm-lint.sh, fm-install-shellcheck.sh, fm-cd-pretool-check.sh, fm-cd-command-policy.mjs, backends/herdr-eventwait.py) actually exist in the treepartial rerun of fulltests/*.test.shsuite (20/82 completed with 0 failures before stopping) as a spot-reconfirmation of the already-passing baselinegit ls-remote origin fm/fm-recover-fork-driftandgh-axi pr list— branch not pushed, no PR opened for this changedocs/fork-patches.md:3- The registry's own stated rule ('each fork-only, non-merge commit has one row') is broader than actual practice: only the original 4 pre-advance commits (e623032, 4939c5b, df159d2, 8f3190b) have rows, while later fork-only feature commits in this same change (notably c8496fb, which adds the fork-sync-check mechanism itself) have none; fix commits to already-tracked features (9053fc4, 8a5fd11) instead updated an existing row's rationale in place. This is a repeated, deliberate authorial choice (verified across three separate commits), not an accidental omission, so left as-is.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.