Skip to content

Commit b529e67

Browse files
juniorlovestmhkunchenguidkarotkrissdanielkuykendall23-booptrillium
authored
feat: recover beta-only workflows onto main (#10)
* feat: pin and restore fleet toolchain offline (#4) * feat: standardize fleet secrets on Doppler (#3) * test: make scheduler refill proof deterministic * feat: standardize fleet secrets on Doppler * no-mistakes(review): Captain: Hardened Doppler runner gates and migration review windows * no-mistakes(review): Captain: Enforced workflow runner validation and sealed clock bypass * no-mistakes(review): Captain: Made Doppler workflow parsing refuse uncertain job structures * no-mistakes(review): Captain: Sealed public and fork-exposed Doppler override paths * no-mistakes(review): Captain: Unified Doppler permission across all trust axes * no-mistakes(review): Captain: Made tracked workflow validation complete and fail-closed * no-mistakes: apply CI fixes --------- Co-authored-by: juniorlovestmh <[email protected]> * feat: add offline fleet toolchain mirror * no-mistakes(review): Captain: restore now uses validated captured npm bin mappings --------- Co-authored-by: juniorlovestmh <[email protected]> * feat(mobile-mode): add verified Moshi Pro mobile review workflow (#6) * docs: separate current guidance from verification evidence (kunchenguid#994) * docs: separate current guides from verification * no-mistakes(review): Restore Herdr 0.7.5 restart-reclaim verification evidence * fix: preserve Claude watcher continuity across Stop hooks (kunchenguid#997) * feat(claude): Stop-owned tokenless watcher continuity via asyncRewake auto-arm Claude primaries (main home and marked secondmate homes) no longer depend on the model remembering to re-arm the watcher after each wake. A tracked Stop asyncRewake hook (bin/fm-claude-stop-autoarm.sh, timeout 28800s) fires on every turn end, claims one home-scoped single-flight owner, foregrounds bin/fm-watch-arm.sh inside the hook-owned process tree, and translates an actionable close or typed watcher failure into exactly one exit-2 rewake. The hook scopes to genuine primary checkouts, requires the session lock to be held by its own harness ancestor, stays inert while AFK owns triage or the home is idle, and hands AFK transitions mid-cycle to the daemon without rewaking. The synchronous turn-end guard gains a --claude cooperative mode: it ignores stop_hook_active (true on every post-continuation stop, which is what re-opened the 2026-07-21 blind window), waits briefly for a watcher health proof, a live auto-arm owner claim, or a fresh rewake epoch, and re-blocks only when the auto-arm genuinely failed to establish - bounded to 3 consecutive blocks per session, safely below Claude Code's 8-block override, then a degraded allow with a visible systemMessage. Codex keeps the previous one-block loop guard byte-identically, and Pi, OpenCode, and Grok adapters are untouched. Continuity PreToolUse gate and durable wake queue are preserved; the gate's recovery guidance now names the Stop-owned re-arm and reserves manual background arms for auto-arm failure. Claude supervision protocol, harness-adapters facts, architecture, configuration, and continuity docs updated; docs/turnend-guard.md records the 2026-07-24 Claude 2.1.218 contract revalidation (tokenless multi-cycle rewake, no-dedup, timeout process-group kill, 8-block cap, interactive non-stall) and the 2.1.219 product live E2Es. Regression matrix: hermetic tests cover scope, identity, AFK, need, single-flight, translation, guard cooperation, budget, and registration; the new live E2E proves two full tokenless auto-arm rewake cycles with zero model arm commands; Pi and OpenCode Option B live E2Es pass unchanged. * no-mistakes(review): Fix Claude X-mode auto-arm continuity backstop * no-mistakes(review): Remove unsupported Claude contract-lab verification claims * no-mistakes(document): Update Claude auto-arm continuity documentation * fix(herdr): clean stale projections at session start (kunchenguid#996) * Clean stale Herdr projections at session start * no-mistakes(document): Document stale Herdr session-start projection cleanup * no-mistakes(review): Enforce locked exact Herdr projection cleanup * no-mistakes(review): Fail closed on unverified session lock ownership * no-mistakes(review): Serialize session lock acquisition atomically * no-mistakes(document): Align session-start and Herdr cleanup documentation * no-mistakes(document): Generalize lock-refusal diagnostics * no-mistakes(lint): Avoid reserved keyword in concurrency test * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * fix: recover Claude supervision without watcher-status gate (kunchenguid#1001) * fix: recover Claude supervision at session start * fix: remove Claude watcher-status command gate * no-mistakes(document): docs: remove stale continuity gate references * fix: make quota-aware profile selection agent-owned (kunchenguid#1018) * Replace quota dispatch selector instructions * no-mistakes(review): Align bootstrap docs with agent-owned dispatch selection * fix(bin): remove vestigial dispatch selector (kunchenguid#1026) * remove vestigial dispatch selector * no-mistakes(review): Synchronize isolation proof and portable shard evidence * no-mistakes(review): Correct shard history and proof archive date * no-mistakes(review): Remove reintroduced selector documentation reference * no-mistakes(document): Remove stale dispatch strategy documentation * docs(agents): drop superseded interim quota-window rule (kunchenguid#1039) quota-axi 0.1.13 emits schemaVersion 2 with a quotaSemantics object per provider, so the successor named in the interim rule has landed and the rule's own removal condition is satisfied. Keep the ownership clause so quota-axi remains the single owner of how model or product windows relate to bounding account windows, and drop the interim weakest-headroom instruction. The unknown-semantics case is already covered by the existing requirement to stop and report a candidate whose applicable quota data or interpretation cannot be established. Drop the matching assertion phrase from tests/fm-instruction-owners.test.sh; the retained ownership phrase still asserts. * fix(tmux): scope busy detection and recognize current Claude turns (kunchenguid#1049) * fix(tmux): scope Claude busy detection by harness * no-mistakes(review): Separate verified and fallback busy signatures * no-mistakes(test): Scope busy signatures to supplied harnesses * no-mistakes(document): Document harness-scoped busy detection * feat: add verified Kimi crewmate adapter (kunchenguid#1047) * Add verified Kimi crewmate harness adapter * no-mistakes(review): Scope Kimi moon detection to spinner lines * no-mistakes(review): Match only complete Kimi spinner rows * no-mistakes(review): Resolve Kimi binary portably before pane creation * no-mistakes(document): Align Kimi adapter documentation * no-mistakes(lint): Suppress false-positive ShellCheck warning for sourced watcher override * Fix Kimi busy spinner detection * no-mistakes(review): Recognize Kimi session-lock ancestry and holders * no-mistakes(review): Scope pending-reply Kimi busy detection by harness * no-mistakes(document): Correct Kimi spinner capture documentation * no-mistakes(document): Clarify optional Kimi spinner whitespace * no-mistakes(lint): Silence intentional pending-reply test stub warnings * test: align rebased Kimi busy fixtures * no-mistakes: apply CI fixes * Reconcile Kimi busy detection after per-harness scoping * no-mistakes(review): Clarify observed Kimi spinner whitespace contract * no-mistakes(document): Clarify Kimi harness documentation * fix: harden Kimi submission and spinner matching (kunchenguid#1058) * fix kimi pointer submission and spinner conformance * no-mistakes(review): Preserve Kimi submit target ownership guard * feat(bin): add guarded Kimi turn-end wake (kunchenguid#1059) * Add guarded Kimi turn-end hook * no-mistakes(review): Require jq before installing Kimi turn-end hook * no-mistakes(review): Expose jq inside isolated Kimi test fixtures * no-mistakes(review): Preserve Kimi config boundaries during hook removal * no-mistakes(review): Document Kimi removal newline safeguard * no-mistakes(document): Document Kimi shared-home preservation * fix(tmux): classify bordered composers across all rows (kunchenguid#1066) * Fix structural tmux composer reading * Verify Calm compatibility with Pi 0.82 * no-mistakes(review): Harden structural composer classification boundaries * no-mistakes(review): Refresh composer and Kimi regression fixtures * no-mistakes(review): Fail closed on unbounded composer edges * no-mistakes(review): Enforce aligned composer geometry safely * no-mistakes(review): Make composer ambiguity locale-safe * no-mistakes(review): Preserve ambiguity through composer submission * no-mistakes(review): Carry composer proof through retries * no-mistakes(document): Document structural tmux composer delivery guarantees * no-mistakes: apply CI fixes * feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145) * feat: add verified pi-signed adapter * no-mistakes(review): Correct pi-signed maintainer verification date * no-mistakes(review): Correct remaining pi-signed verification dates * no-mistakes(review): Preserve authoritative pi-signed runtime identity * no-mistakes(document): Document pi-signed shared adapter semantics * no-mistakes: apply CI fixes * fix(pi): rearm watcher across session transitions (kunchenguid#1166) * fix(pi): rearm watcher across same-process session transitions Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement as well as terminal quit. The primary watcher extension latched a module-level stopping flag on every shutdown, so a replacement session in the same process could not arm monitoring until Pi restarted. Own arm authority per session generation so only the active live generation may start, stop, or rearm the child. Replacement sessions can arm again without restarting Pi, stale prior-generation callbacks cannot mutate the active cycle, and real quit still blocks late rearm. * no-mistakes(review): Preserve Pi generation isolation and exit cleanup * no-mistakes(document): Correct Pi watcher transition documentation * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171) * fix(grok): adapt Stop continuation to runtime capability * no-mistakes(review): Reject ambiguous Grok Stop payloads * no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions * no-mistakes(review): Enforce exact tmux cleanup selectors * no-mistakes(test): Fix historical tmux fixture and validate Grok Stop * no-mistakes: apply CI fixes * fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093) * fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2 fm-brief.sh built each Definition-of-done block and the not-enabled Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS /bin/bash) the lexer scans for the command substitution's closing `)` textually and tracks quote state through the heredoc body, so a single apostrophe, unbalanced quote, or unbalanced paren in that prose breaks parsing of the whole script. Every ship-brief scaffold (no-mistakes, direct-PR, local-only) failed with `unexpected EOF while looking for matching )`. Bash 4+ parses it fine, so the breakage stayed invisible everywhere except stock macOS. Replace all four command-substitution heredocs with `IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)` wrapper and the entire defect class regardless of future prose, and preserves the variable expansion the direct-PR and local-only bodies need. `read` keeps the heredoc's trailing newline that `$(...)` used to strip, so trim one newline to keep every generated brief byte-identical to prior output. Guard the structure, not one historical phrase: a new test rejects any heredoc nested in a command substitution anywhere in fm-brief.sh, where the old assertion pinned a single apostrophe phrase and so missed the reintroduction. Extend the stock-macOS Bash CI job from parsing one script to the whole maintained shell surface (bin/*.sh, bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file set so parse scope and lint scope cannot drift apart. * no-mistakes(review): Captain: harden Bash structure and inventory guards * no-mistakes(document): Align stock macOS Bash contributor checks * no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * feat(bin): inherit backend config into secondmate homes (kunchenguid#1219) * Inherit config/backend into secondmate homes with deliberate-override preservation Add backend to the shared inheritable config allowlist so launch, locked bootstrap, and config-push converge a primary pin into secondmate homes as each home local future-spawn default. Track last-inherited bytes in a private state provenance marker so deliberate per-home overrides survive present and absent primary convergence, keep --backend and FM_BACKEND stronger, and extend the existing inheritance tests plus docs and skill claims. * no-mistakes(review): Preserve equal unprovenanced backend overrides * no-mistakes(review): Preserve symlink overrides and verify spawn precedence * no-mistakes(review): Snapshot backend inheritance for consistent provenance * no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence * no-mistakes(document): Document inherited backend override preservation * fix: restore primary-authoritative backend inheritance after document regression The document step reintroduced provenance and deliberate per-home override semantics after review had simplified config/backend to plain primary-authoritative allowlist membership. Restore the primary-always-wins path: present overwrites, absent removes, no provenance marker, and docs/tests match that contract. * no-mistakes(review): Add divergent backend precedence regression fixtures * no-mistakes(document): Document backend inheritance contract * fix(pi): remove Calm's upper version ceiling (kunchenguid#1226) * fix(pi): remove Calm's exclusive Pi upper-version ceiling tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs described that range as "supported" rather than verified evidence. The Calm CHANGELOG shows no API introduced at either version, so there is no evidence for a real minimum; the presentation adapters already probe the exact method they patch rather than checking a version. Replace the allowlist with dated version evidence that never rejects a newer Pi, and make each presentation adapter degrade independently with a diagnostic if a future Pi removes its API, instead of the whole Calm extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1 through 0.82.0" phrasing to state it as verified evidence, not a ceiling. * no-mistakes(review): Probe missing Calm adapter exports safely * no-mistakes(document): Document Calm's unbounded Pi compatibility * fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204) * fix(guard): allow session-local todo tools in the primary The delegation-shape guard denied TaskCreate and TaskUpdate because their normalized names contain the `task` stem. Those tools write only the harness's session-local todo list, which has no executor: it spawns no agent, allocates no worktree, registers no schedule, and starts nothing that outlives the session. That is not the unaccounted work the guard exists to stop, so the stem match was a false positive, and the deny text told the primary to run bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry. Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only observe or stop existing work. Both lists stay exact-name so neither can widen by substring. Tests cover the two allowed names and six near-miss names that a substring or shortened-stem widening would release; both mutations were watched red. * no-mistakes(review): drop session-local todo tools from recommended deny list * no-mistakes: apply CI fixes * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206) * fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid fm_harness_ancestry_pid() previously returned the first ancestor process whose command matched a verified harness name. Claude Code's Stop hook fires as a bg-spare worker several levels below the session's actual lock-owning claude process (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock)), so the first match was the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self() then never matched state/.lock, and the Claude Stop auto-arm silently treated its own primary session as an unrelated live owner and never armed the watcher. The walk now keeps going past a claude-named match, looking for a still more ancestral claude-named match, and stops the instant a non-match follows an already-found match (bounding it to a contiguous run rather than the literal ancestry top, so an unrelated claude-named process further up the real process tree is never mistaken for part of this session's own nested chain). Every other harness keeps the original first-match-wins behavior, since e.g. Pi's shared signed-wrapper ancestry actually holds the session at the inner engine pid, not an outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper bg-spare chain. * no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim * no-mistakes: apply CI fixes * fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212) * fix: confirm watcher startup on MSYS * no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test * no-mistakes(review): validate OpenCode ready timeout, make uname cache internal * fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195) * fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates Crewmate panes are created by a long-lived tmux/herdr daemon that does not inherit firstmate's current environment. When firstmate runs under a non-default CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare `claude` in the crewmate pane fell back to the default ~/.claude store and launched unauthenticated, blocking the crewmate before it could do any work. fm-spawn now prefixes the claude launch with firstmate's own resolved CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config store firstmate is authenticated with. An unset value is the single-store default and adds no prefix; non-claude harnesses are unaffected. Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set, omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test helper so launch assertions no longer depend on the developer's environment. * no-mistakes: apply CI fixes * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * fix(bin): normalize relative durable paths (kunchenguid#1256) * fix(bin): handle dash-leading harness process names (#2) * fix: handle dash-leading harness process names * no-mistakes(review): Make dash-leading harness regression hermetic * fix: preserve secondmate reply routes across relative homes Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved. Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory. * no-mistakes(review): Preserve absolute overrides and normalize relative durable paths * no-mistakes(review): Normalize relative home before deriving durable paths * no-mistakes(document): Document relative durable-path normalization * no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization * no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <[email protected]> * Clarify follow-up routing during validation (kunchenguid#1277) * fix: honor concrete approval for project operations (kunchenguid#1272) * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * docs: add captain-approved project operation exception to hard rule 1 Firstmate stays read-only over projects by default, but when the captain clearly approves a concrete project operation and scope in the moment, firstmate may perform exactly that approved operation with its own tools. The approval is never inferred, broadened, or standing, and it does not relax the existing force, discard, unlanded-work, or merge-authority boundaries. * no-mistakes(review): Clarify captain-approved project operation boundaries * no-mistakes(document): Clarify captain-approved project operation scope * docs: cover directories and preserve the operation-or-scope alternative Widen the captain-approved project operation exception in AGENTS.md to files or directories, and restore the explicit operation-or-scope alternative that a prior pipeline auto-fix had collapsed into "and". Rework project-management SKILL.md's Remove section, which previously told firstmate to refuse project removal until a guarded helper existed; that helper was never built, so the text directly contradicted the new instruction-only exception. It now points at the exception plus the existing removal preflight it still requires unchanged. Update the one instruction-owners test assertion that hard-coded the sentence removed above, so the suite tracks current, not obsolete, text. * no-mistakes(review): Align project removal preflight with approved exception * no-mistakes(document): Align project removal documentation with approved exception * fix: restore removal test byte-for-byte and preserve the default sentence tests/fm-instruction-owners.test.sh had been changed to assert different text; restore it byte-for-byte to origin/main. project-management SKILL.md's Remove section now keeps the exact default "Never issue a raw removal command from Firstmate." sentence that test still asserts, immediately followed by the already-approved captain-operation-or-scope exception, so the default and the exception both stay explicit and consistent. * no-mistakes(document): Align project-write boundary documentation * fix(skills): route new project intake through secondmate scopes (kunchenguid#1275) * Route project intake through secondmate scopes * no-mistakes(test): Guard all main-home project registry mutations * no-mistakes(document): Consolidate secondmate routing documentation * no-mistakes: apply CI fixes * Restore new-project routing scope * no-mistakes(document): Clarify secondmate routing for new-project intake * no-mistakes: apply CI fixes * fix: scope validation corrections by accepted behavior (kunchenguid#1281) * fix: scope validation corrections by accepted behavior * no-mistakes(review): Classify stale delivery evidence as an autonomous correction * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * fix(watch): escalate busy workers with no completed turn (kunchenguid#1286) * fix(watch): bound how long a busy pane may run with no completed turn A busy pane (backend busy state or the harness's rendered footer) was unconditional, unbounded proof of liveness in every escalation path, so a hung foreground tool call behind a busy signature could run for hours undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic- backtracking regex hung one bash call for 25h behind an unchanging "Working..." footer). FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may run with no completed turn (state/<id>.turn-ended, or its spawn record before any turn has completed). Past the bound, busy_turn_over_age routes the pane through the existing wedge_timer_check, reusing the identical stale reason, escalation counter, and demand-deep-inspection marker for human inspection only - never an automatic interrupt, signal, or restart of the worker or its tool process. A completed turn resets the age. Reproduced end-to-end against the real installed Pi TUI: a foreground `sleep 999999` bash call with no timeout renders the actual busy footer, and two captures ~15s apart show the elapsed counter changing the pane hash while the same turn stays unfinished. Running the pre-fix watcher against the real captures showed it never starts a wedge timer no matter how long the pane stays busy; the fixed watcher starts and escalates the timer through the same mechanism, while the real hung process remained untouched and alive throughout. * no-mistakes(review): fix: parse enriched AFK stale reasons * no-mistakes(review): fix: preserve enriched wedges during AFK supervision * no-mistakes(review): fix: route all enriched AFK wedges * no-mistakes(document): Clarify busy-turn age supervision documentation * fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261) A name-by-name list of config/ entries silently stops ignoring any new or home-local file placed there, which makes the working tree read as dirty and blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md already documents config/ as captain-private and gitignored as a category; this makes .gitignore match that contract. * fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304) The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps .gitignore for a specific spelling of the config/ ignore pattern. It fails on a semantically equivalent pattern like config/** and does not prove Git actually ignores anything, per the completed source-content-test audit. Replace it with a real git check-ignore control test on a generated unrelated path, and strengthen the existing directory-coverage test with generated unpredictable direct and nested config/ paths. * feat: bound and consolidate startup memory during stow (kunchenguid#1303) * Add bounded startup memory curation * no-mistakes(review): Record reproducible stow verification evidence * no-mistakes(review): Validate inherited secondmate stow evidence * no-mistakes(document): Document editable startup-memory budget propagation * no-mistakes(review): Captain: restored ADHD coverage and Doppler CLI validation * no-mistakes(review): Classify CLI-only Doppler jobs as injecting * no-mistakes(document): Refresh test-isolation documentation terminology * fix: satisfy pinned shellcheck for merge tests * docs: add Moshi mobile review mode Red proof: the pre-change public agent surface had no Moshi/mobile-mode trigger or durable Browser Preview handoff. Verification: fm-doc-audience-check passes; focused captain-translation and documentation-audience tests pass; fm-test-run --changed passes 26 of 27 selected scripts. The unrelated live Pi 0.83 Calm /export interaction timed out, while this branch changes no Calm or Pi test surface. * no-mistakes(review): Captain: align Chat View with numbered Firstmate fallback * no-mistakes(test): Documented unavailable Moshi dogfood; preserved captain checklist * no-mistakes(document): Consolidated Moshi documentation ownership --------- Co-authored-by: Kun Chen <[email protected]> Co-authored-by: Christopher McKay <[email protected]> Co-authored-by: Daniel Kuykendall IV <[email protected]> Co-authored-by: Trillium Smith <[email protected]> Co-authored-by: Unknownzed <[email protected]> Co-authored-by: lhalbert <[email protected]> Co-authored-by: AG <[email protected]> Co-authored-by: deeto15 <[email protected]> Co-authored-by: juniorlovestmh <[email protected]> * feat(brief): add self-authenticating launch provenance (#7) * fix: make quota-aware profile selection agent-owned (kunchenguid#1018) * Replace quota dispatch selector instructions * no-mistakes(review): Align bootstrap docs with agent-owned dispatch selection * feat: route crew dispatch using quota-window pace (kunchenguid#1172) * Consume quota-axi pace signals in dispatch profile array selection. Add quota-array-dispatch as the single owner of the pace-aware candidate choice, keep AGENTS.md to the intake boundary and load trigger, and cover the acceptance cases with sanitized schemaVersion 3 fixtures. * no-mistakes(review): Stop and report genuine quota dispatch ties * no-mistakes(document): Document quota pace freshness and uncertainty * test: stabilize tmux teardown conformance baseline (kunchenguid#1209) * fix(test): pin teardown tmux baseline to historical kill selectors merge-base HEAD main collapses to HEAD after the exact-selector change lands on the default branch, so the old teardown fixture was accidentally exercising current exact targets. Resolve a content-historical permissive tmux adapter from first-parent history and force that post-squash topology inside the conformance case so main and feature branches keep the same old-vs-new contract. * no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings * docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197) Cut the runtime skill to the compact pace-aware selection procedure plus minimum owner pointers. Keep every distinct decision rule and move expanded acceptance scenarios to deterministic fixture ownership assertions. Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction). * fix: preserve dispatch identity across authentication checks (kunchenguid#1233) * fix: preserve dispatch harness identity * no-mistakes(review): Fix Grok counterfactual tuple validation * no-mistakes(document): Scope dispatch authentication to selected tuple * fix: restore dispatch instruction budget * no-mistakes(review): Scope dispatch authentication after candidate selection * refactor(skills): make Bearings chat-only by default (kunchenguid#1136) * Add internal status skill * no-mistakes(document): register /status skill in documentation-audiences inventory * no-mistakes(lint): replace grep|wc -l with grep -c in status skill test * test: silence literal status skill patterns * Refactor bearings default to chat-only --------- Co-authored-by: Kun Chen <[email protected]> * test: replace source assertions with behavioral coverage (kunchenguid#1282) * test: remove source-content assertions * no-mistakes(review): Replace source assertions with runtime behavior coverage * no-mistakes(review): Isolate Kimi task temp runtime coverage * no-mistakes(document): Refresh test cleanup documentation * no-mistakes: apply CI fixes * no-mistakes(review): Captain: restored ADHD coverage and Doppler CLI validation * fix: satisfy pinned shellcheck for merge tests * fix brief launch provenance and stale regeneration * no-mistakes(review): Captain: fixed atomic regeneration and guidance; focused tests pass, shellcheck unavailable * no-mistakes(lint): Captain: ordered overlapping lint case patterns --------- Co-authored-by: Kun Chen <[email protected]> Co-authored-by: deeto15 <[email protected]> Co-authored-by: juniorlovestmh <[email protected]> * fix(no-mistakes): make lint command valid YAML --------- Co-authored-by: juniorlovestmh <[email protected]> Co-authored-by: Kun Chen <[email protected]> Co-authored-by: Christopher McKay <[email protected]> Co-authored-by: Daniel Kuykendall IV <[email protected]> Co-authored-by: Trillium Smith <[email protected]> Co-authored-by: Unknownzed <[email protected]> Co-authored-by: lhalbert <[email protected]> Co-authored-by: AG <[email protected]> Co-authored-by: deeto15 <[email protected]>
1 parent 87f25e0 commit b529e67

17 files changed

Lines changed: 1157 additions & 18 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
name: mobile-mode
3+
description: >-
4+
Shape captain-facing Firstmate messages and review handoffs for a phone, especially when Moshi is the active surface.
5+
user-invocable: false
6+
metadata:
7+
internal: true
8+
---
9+
10+
# mobile-mode
11+
12+
Load this skill when the captain says they are in mobile mode or identifies Moshi as the active surface.
13+
Continue following it until the captain says they are back on desktop or requests normal mode.
14+
15+
This is a presentation profile over the same host-side Firstmate session.
16+
It does not create a Moshi runtime backend, supervision path, authority channel, or webhook integration.
17+
The always-loaded [`i-have-adhd`](../i-have-adhd/SKILL.md) contract remains the owner of general captain-facing presentation, and `AGENTS.md` section 9 remains the owner of outcome translation and approval escalation.
18+
This skill owns only the mobile delta and the review-surface choice.
19+
20+
## Message shape
21+
22+
- Apply the `i-have-adhd` outcome-first rule, then keep the outcome, consequence, evidence, and requested action within one ordinary phone scroll whenever the required facts fit.
23+
- Make choices answerable with one low-typing reply such as `1`, `2`, `yes`, `merge`, or `hold`.
24+
- Number choices, put the recommendation first, and end with the exact short reply that will select it.
25+
- Keep full `https://...` pull-request links under section 9's existing rule so the captain can open the review directly.
26+
- Put long logs and secondary evidence in the existing private report, then summarize the consequence in chat.
27+
- Do not require terminal copy mode, pane navigation, punctuation-heavy commands, or multi-step text entry to answer a decision.
28+
29+
## Review-surface choice
30+
31+
Use plain chat for a simple decision or whenever a rich surface is unnecessary or unavailable.
32+
Use a host-local Lavish surface through Moshi Pro Browser Preview when several options or structured feedback benefit from a touch-friendly review.
33+
Follow the operator runbook in [`docs/moshi-mobile-review.md`](../../../docs/moshi-mobile-review.md).
34+
35+
After Lavish starts on the host, tell the captain to open Browser Preview in Moshi and choose the Lavish server.
36+
Never present a raw `127.0.0.1`, `localhost`, `file://`, or desktop-only LAN URL as if the phone can open it directly.
37+
If Browser Preview is unavailable, restate the complete decision in chat with numbered replies instead of suggesting public sharing.
38+
39+
Use Moshi Pro Diff to inspect the connected working tree, while keeping a full HTTPS pull-request link in chat for a hosted PR review.
40+
Use Chat View only when Moshi recognizes the active agent and session. For unsupported prompts, incomplete cards, or an unrecognized session, keep the same Moshi/Firstmate session and present the complete fallback as concise numbered Firstmate chat; the terminal remains the source of truth, but is not a separate mobile handoff surface.
41+
42+
Private fleet reviews stay host-local.
43+
Never invoke or suggest `lavish-axi share` for private fleet state, even with a password.
44+
Public sharing of separately sanitized public material requires an explicit request and the ordinary outward-facing consent boundary.
45+
46+
## `moshi-hook` authority
47+
48+
The already-installed `moshi-hook` may surface the running agent's native inbox events and approvals in Moshi or Apple Watch.
49+
An approval button may answer only the exact native agent prompt that Moshi can map safely to the same live session.
50+
It never authorizes a Firstmate merge, product or scope decision, destructive or irreversible action, credential use, permission change, or security-sensitive choice.
51+
Route those decisions through Firstmate chat under the existing authority contract.
52+
53+
Do not add a Firstmate-to-Moshi webhook, change hook configuration, or place secrets in notification summaries.
54+
When an approval is unavailable, ambiguous, or unsupported, return to the terminal or ask through Firstmate chat without weakening the underlying boundary.

.no-mistakes.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ document:
3434
# security, Herdr, tmux, and lifecycle coverage). A full-suite override here
3535
# would duplicate CI and defeat the targeted Test contract.
3636
commands:
37-
lint: 'shellcheck_dir=$(mktemp -d "${TMPDIR:-/tmp}/fm-no-mistakes-shellcheck.XXXXXX") && trap '\''rm -rf "$shellcheck_dir"'\'' EXIT && bin/fm-install-shellcheck.sh "$shellcheck_dir" >/dev/null && PATH="$shellcheck_dir:$PATH" exec bin/fm-lint.sh'
37+
lint: >-
38+
shellcheck_dir=$(mktemp -d "${TMPDIR:-/tmp}/fm-no-mistakes-shellcheck.XXXXXX") &&
39+
trap 'rm -rf "$shellcheck_dir"' EXIT &&
40+
bin/fm-install-shellcheck.sh "$shellcheck_dir" >/dev/null &&
41+
PATH="$shellcheck_dir:$PATH" exec bin/fm-lint.sh
3842
3943
# Keep test evidence out of this repo; it stays in a temp dir instead.
4044
test:

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ Load `stuck-crewmate-recovery` after a stale wake, looping or confused pane, ans
387387
## 9. Escalation and captain etiquette
388388

389389
Load `i-have-adhd` before every captain-facing response; the skill owns presentation shape while this section owns outcome translation and internal-vocabulary rewriting.
390+
When the captain says they are in mobile mode or identifies Moshi as the active surface, load `mobile-mode`; it owns the mobile presentation and review handoff delta until the captain returns to desktop or normal mode.
390391

391392
**Talk in outcomes, not mechanics.**
392393
Every captain-facing message must translate internal state into the project outcome, consequence, and next decision.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ Firstmate's skills live in two separate places with different audiences:
200200
- [docs/architecture.md](docs/architecture.md) - maintainer architecture for the crew, supervision, worktrees, secondmates, and project modes.
201201
- [docs/configuration.md](docs/configuration.md) - environment variables, `FM_HOME`, runtime backend selection, optional X mode, the files you set, and harness support.
202202
- [docs/calm.md](docs/calm.md) - current Pi `/calm` behavior and supported presentation limits.
203+
- [docs/moshi-mobile-review.md](docs/moshi-mobile-review.md) - host-local Moshi Pro Preview, Diff, Chat View, and private mobile-review fallbacks.
203204
- [docs/wedge-alarm.md](docs/wedge-alarm.md) - configure the active alert for an away-mode escalation delivery that gets stuck.
204205
- [docs/tmux-backend.md](docs/tmux-backend.md) - current setup and limits for the tmux reference backend.
205206
- [docs/herdr-backend.md](docs/herdr-backend.md) - current setup, safety boundaries, and limits for the experimental Herdr backend.

bin/fm-brief.sh

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
# description, acceptance criteria, and context, and may adjust other sections
77
# when the task genuinely deviates (e.g. working an existing external PR instead
88
# of shipping a new one).
9-
# Usage: fm-brief.sh <task-id> <repo-name> [--scout] [--herdr-lab]
10-
# fm-brief.sh <task-id> --secondmate {<project>...|--no-projects}
9+
# Usage: fm-brief.sh <task-id> <repo-name> [--scout] [--herdr-lab] [--force-regenerate]
10+
# fm-brief.sh <task-id> --secondmate {<project>...|--no-projects} [--force-regenerate]
1111
# --scout writes the scout contract instead: the deliverable is a report at
1212
# data/<task-id>/report.md (no branch, no push, no PR) and the worktree is scratch.
1313
# --secondmate writes a persistent secondmate charter. The project list
@@ -26,6 +26,12 @@
2626
# The flag must be explicit because {TASK} is filled after scaffolding and the
2727
# caller-supplied repo string cannot reliably identify this repo. Briefs made
2828
# without it carry a loud declaration so an omitted contract cannot be silent.
29+
# Every generated brief carries a versioned scaffold safety marker.
30+
# When an existing brief is present, the refusal reports whether that marker
31+
# is current but never treats the marker as proof that the task text is fresh.
32+
# --force-regenerate renders a fresh scaffold before archiving an existing
33+
# brief beside it and installing the replacement; it never silently clobbers
34+
# the previous content.
2935
# For ship tasks, the definition of done is shaped by the project's delivery mode
3036
# (data/projects.md via fm-project-mode.sh; see the project-management skill
3137
# and AGENTS.md task lifecycle):
@@ -44,7 +50,7 @@
4450
# it carries the AGENTS.md authoring bar (widely useful knowledge only, pointers
4551
# over copied detail) and has the crewmate add the fm-ensure-agents-md.sh
4652
# self-governance section when a touched project AGENTS.md lacks it.
47-
# Refuses to overwrite an existing brief.
53+
# Refuses to overwrite or silently reuse an existing brief.
4854
set -eu
4955

5056
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -94,17 +100,24 @@ fi
94100
KIND=ship
95101
HERDR_LAB=0
96102
NO_PROJECTS=0
103+
FORCE_REGENERATE=0
97104
POS=()
98105
for a in "$@"; do
99106
case "$a" in
100107
--scout) KIND=scout ;;
101108
--secondmate) KIND=secondmate ;;
102109
--herdr-lab) HERDR_LAB=1 ;;
103110
--no-projects) NO_PROJECTS=1 ;;
111+
--force-regenerate) FORCE_REGENERATE=1 ;;
104112
*) POS+=("$a") ;;
105113
esac
106114
done
107-
ID=${POS[0]}
115+
ID=${POS[0]:-}
116+
117+
if [ -z "$ID" ]; then
118+
echo "error: task id is required" >&2
119+
exit 1
120+
fi
108121

109122
if [ "$KIND" = secondmate ] && [ "$HERDR_LAB" -eq 1 ]; then
110123
echo "error: --herdr-lab applies only to crewmate ship or scout briefs" >&2
@@ -117,8 +130,73 @@ if [ "$NO_PROJECTS" -eq 1 ] && [ "$KIND" != secondmate ]; then
117130
fi
118131

119132
BRIEF="$DATA/$ID/brief.md"
120-
[ -e "$BRIEF" ] && { echo "error: $BRIEF already exists" >&2; exit 1; }
121-
mkdir -p "$DATA/$ID"
133+
BRIEF_SAFETY_MARKER='<!-- firstmate-brief-scaffold-safety:v1 -->'
134+
BRIEF_OUTPUT="$BRIEF"
135+
136+
cleanup_staged_brief() {
137+
if [ "$BRIEF_OUTPUT" != "$BRIEF" ]; then
138+
rm -f -- "$BRIEF_OUTPUT"
139+
fi
140+
}
141+
142+
brief_has_current_safety_marker() {
143+
[ -f "$BRIEF" ] && grep -Fqx "$BRIEF_SAFETY_MARKER" "$BRIEF"
144+
}
145+
146+
prepare_brief_path() {
147+
mkdir -p "$DATA/$ID"
148+
if [ ! -e "$BRIEF" ]; then
149+
return 0
150+
fi
151+
152+
if [ "$FORCE_REGENERATE" -ne 1 ]; then
153+
echo "error: $BRIEF already exists; refusing to overwrite or silently reuse it" >&2
154+
if brief_has_current_safety_marker; then
155+
echo "error: current scaffold safety marker is present, but task freshness is unverified" >&2
156+
echo "error: inspect $BRIEF and verify it intentionally, or rerun with --force-regenerate to archive it and write a fresh scaffold" >&2
157+
else
158+
echo "error: missing current scaffold safety marker; this brief may predate current safety contracts" >&2
159+
echo "error: Do not launch this brief unchanged; rerun the same scaffold command with --force-regenerate to archive it and write a fresh scaffold" >&2
160+
fi
161+
return 1
162+
fi
163+
164+
BRIEF_OUTPUT=$(mktemp "$DATA/$ID/.brief.md.XXXXXX") || {
165+
echo "error: could not stage regenerated brief: $BRIEF" >&2
166+
return 1
167+
}
168+
trap cleanup_staged_brief EXIT
169+
}
170+
171+
install_staged_brief() {
172+
local archive_base archive timestamp suffix
173+
[ "$BRIEF_OUTPUT" != "$BRIEF" ] || return 0
174+
175+
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
176+
archive_base="$BRIEF.archive-$timestamp"
177+
archive=$archive_base
178+
suffix=1
179+
while [ -e "$archive" ]; do
180+
archive="$archive_base.$suffix"
181+
suffix=$((suffix + 1))
182+
done
183+
if [ -e "$BRIEF" ]; then
184+
mv -- "$BRIEF" "$archive" || {
185+
echo "error: could not archive existing brief: $BRIEF" >&2
186+
return 1
187+
}
188+
fi
189+
if ! mv -- "$BRIEF_OUTPUT" "$BRIEF"; then
190+
if [ -e "$archive" ]; then
191+
mv -- "$archive" "$BRIEF" || echo "error: could not restore existing brief: $BRIEF" >&2
192+
fi
193+
echo "error: could not install regenerated brief: $BRIEF" >&2
194+
return 1
195+
fi
196+
BRIEF_OUTPUT="$BRIEF"
197+
trap - EXIT
198+
[ -e "$archive" ] && echo "archived existing brief: $archive"
199+
}
122200

123201
shell_quote() {
124202
printf "'"
@@ -140,6 +218,7 @@ if [ "$NO_PROJECTS" -eq 1 ]; then
140218
else
141219
[ -n "$SECONDMATE_PROJECTS" ] || { echo "error: --secondmate requires at least one project, or --no-projects for a project-less home" >&2; exit 1; }
142220
fi
221+
prepare_brief_path || exit 1
143222
SECONDMATE_CHARTER=${FM_SECONDMATE_CHARTER:-"{TASK}"}
144223
SECONDMATE_SCOPE=${FM_SECONDMATE_SCOPE:-${FM_SECONDMATE_CHARTER:-"{TASK}"}}
145224
if [ "$NO_PROJECTS" -eq 1 ]; then
@@ -149,7 +228,8 @@ else
149228
PROJECT_CLONES_BODY=$(printf '%s\n' "$SECONDMATE_PROJECTS" | tr ' ' '\n' | sed 's/^/- /')
150229
PROJECT_CLONES_NOTE="The projects above are local clones for work you supervise; they are not an exclusive ownership claim."
151230
fi
152-
cat > "$BRIEF" <<EOF
231+
cat > "$BRIEF_OUTPUT" <<EOF
232+
$BRIEF_SAFETY_MARKER
153233
You are a persistent second mate managed by the main firstmate. Work on your own; do not wait for a human.
154234
155235
# Charter
@@ -205,6 +285,7 @@ When you have no assigned or in-flight work after that reconciliation, go idle a
205285
An empty queue is a healthy resting state, not a cue to invent work: never spawn a survey, audit, or any self-directed "find work" task on your own initiative.
206286
If this charter cannot be carried out, append \`blocked: {why}\` or \`failed: {why}\` to the main status file and stop.
207287
EOF
288+
install_staged_brief || exit 1
208289
if [ "$SECONDMATE_CHARTER" = "{TASK}" ]; then
209290
echo "scaffolded: $BRIEF (secondmate charter; replace {TASK})"
210291
else
@@ -213,7 +294,12 @@ fi
213294
exit 0
214295
fi
215296

216-
REPO=${POS[1]}
297+
REPO=${POS[1]:-}
298+
if [ -z "$REPO" ]; then
299+
echo "error: repo name is required for ship and scout briefs" >&2
300+
exit 1
301+
fi
302+
prepare_brief_path || exit 1
217303

218304
if [ "$HERDR_LAB" -eq 1 ]; then
219305
HERDR_LAB_HELPER=$(shell_quote "$FM_ROOT/bin/fm-herdr-lab.sh")
@@ -248,7 +334,8 @@ HERDR_SECTION=${HERDR_SECTION%$'\n'}
248334
fi
249335

250336
if [ "$KIND" = scout ]; then
251-
cat > "$BRIEF" <<EOF
337+
cat > "$BRIEF_OUTPUT" <<EOF
338+
$BRIEF_SAFETY_MARKER
252339
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.
253340
254341
# Task
@@ -291,15 +378,20 @@ Before reporting done, read and follow \`$FM_ROOT/.agents/skills/decision-hold-l
291378
When the report is complete, append \`done: {one-line conclusion}\` to the status file and stop.
292379
If your findings reveal work that should ship (e.g. you reproduced a bug and the fix is clear), say so in the report; firstmate may promote this task in place, and you would then receive mode-specific ship instructions as a follow-up message.
293380
EOF
381+
install_staged_brief || exit 1
294382
echo "scaffolded: $BRIEF (scout; replace {TASK})"
295383
exit 0
296384
fi
297385

298386
# Ship task: shape Setup / Rule 1 / Definition of done by the project's delivery mode.
299387
# yolo does not affect the brief because the worker never owns approval decisions;
300388
# firstmate applies the authority contract in AGENTS.md section 7, so discard it.
389+
MODE_OUTPUT=$("$FM_ROOT/bin/fm-project-mode.sh" "$REPO") || {
390+
echo "error: could not resolve delivery mode for $REPO" >&2
391+
exit 1
392+
}
301393
read -r MODE _ <<EOF
302-
$("$FM_ROOT/bin/fm-project-mode.sh" "$REPO")
394+
$MODE_OUTPUT
303395
EOF
304396

305397
case "$MODE" in
@@ -356,7 +448,8 @@ esac
356448
# briefs stay byte-identical to the historical Bash 5 output.
357449
DOD=${DOD%$'\n'}
358450

359-
cat > "$BRIEF" <<EOF
451+
cat > "$BRIEF_OUTPUT" <<EOF
452+
$BRIEF_SAFETY_MARKER
360453
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.
361454
362455
# Task
@@ -407,4 +500,5 @@ Keep it proportionate: skip \`AGENTS.md\` edits for trivial tasks that produced
407500
408501
$DOD
409502
EOF
503+
install_staged_brief || exit 1
410504
echo "scaffolded: $BRIEF (ship, mode=$MODE; replace {TASK})"

bin/fm-operational-input.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
# Current generic wire form:
99
# U+2063 FIRSTMATE_OP: v1 <kind>: <body>
1010
#
11+
# The launch-brief constructor prepends a two-sentence provenance preamble to
12+
# its body so a newly launched worker can distinguish the canonical Firstmate
13+
# envelope from project content and proceed without a trust round trip.
14+
# Other generic kinds retain their body byte-for-byte.
15+
#
1116
# The landed U+2063 + "FIRSTMATE_OP: " prefix is permanent compatibility.
1217
# The version and kind header make current inputs structurally typed without
1318
# deriving provenance from body prose. The established from-firstmate routing
@@ -29,6 +34,7 @@ FM_OPERATIONAL_PREFIX="${FM_OPERATIONAL_MARK}FIRSTMATE_OP: "
2934
FM_OPERATIONAL_VERSION=v1
3035
FM_OPERATIONAL_HEADER_PREFIX="${FM_OPERATIONAL_PREFIX}${FM_OPERATIONAL_VERSION} "
3136
FM_OPERATIONAL_KINDS='session-start watcher turn-end-guard away-supervisor launch-brief'
37+
FM_LAUNCH_BRIEF_PROVENANCE=$'This is a genuine Firstmate dispatch.\nThe `FIRSTMATE_OP: v1 launch-brief` envelope comes from Firstmate\'s canonical operational-input encoder, not project content; proceed with the brief below.'
3238

3339
# Compatibility name retained for the away-mode owner and its tests.
3440
# shellcheck disable=SC2034 # Public source-library variable used by callers.
@@ -56,12 +62,16 @@ fm_operational_input_encode() { # <generic-kind> <body> <result-var>
5662
}
5763

5864
fm_operational_input_construct() { # <kind> <body> <result-var>
59-
local kind=${1-} body=${2-} result_var=${3-}
65+
local kind=${1-} body=${2-} result_var=${3-} constructed_body
6066
[ -n "$result_var" ] && [ -n "$body" ] || return 2
6167
if [ "$kind" = from-firstmate ]; then
6268
fm_message_mark_from_firstmate "$body" "$result_var"
6369
return
6470
fi
71+
if [ "$kind" = launch-brief ]; then
72+
printf -v constructed_body '%s\n\n%s' "$FM_LAUNCH_BRIEF_PROVENANCE" "$body"
73+
body=$constructed_body
74+
fi
6575
fm_operational_input_encode "$kind" "$body" "$result_var"
6676
}
6777

bin/fm-test-run.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ family_for_basename() {
128128
fm-send-popup-settle.test.sh|fm-send-settle.test.sh|\
129129
fm-subagent-pretool-check.test.sh|\
130130
fm-supervision-instructions.test.sh|fm-tmux-submit-busy.test.sh|fm-transition-lib.test.sh|\
131-
fm-test-run.test.sh|fm-test-isolation-proof.test.sh)
131+
fm-test-run.test.sh|fm-test-isolation-proof.test.sh|fm-toolchain-mirror.test.sh)
132132
printf '%s\n' pure-contract-unit
133133
;;
134134
fm-daemon.test.sh|fm-guard-stale-banner.test.sh|fm-pi-watch-extension.test.sh|\
@@ -685,9 +685,6 @@ families_for_changed_path() {
685685
bin/fm-ff-lib.sh|bin/fm-gotmp*|bin/*pretool*)
686686
printf '%s\n' pure-contract-unit
687687
;;
688-
.agents/skills/*/SKILL.md)
689-
printf '%s\n' pure-contract-unit
690-
;;
691688
.github/workflows/ci.yml|.no-mistakes.yaml)
692689
printf '%s\n' pure-contract-unit
693690
printf '%s\n' real-herdr-gated
@@ -697,6 +694,9 @@ families_for_changed_path() {
697694
docs/examples/doppler-*-job.yml)
698695
printf '%s\n' pure-contract-unit
699696
;;
697+
.agents/skills/*/SKILL.md)
698+
printf '%s\n' pure-contract-unit
699+
;;
700700
.github/*|.tasks.toml|AGENTS.md|CLAUDE.md|CONTRIBUTING.md|\
701701
docs/configuration.md|docs/supervision-protocols/*)
702702
printf '%s\n' pure-contract-unit

0 commit comments

Comments
 (0)