Skip to content

sync: merge upstream kunchenguid/firstmate main (16 commits) - #27

Merged
knowttl merged 17 commits into
mainfrom
fm/fm-upstream-sync-10
Aug 14, 2026
Merged

sync: merge upstream kunchenguid/firstmate main (16 commits)#27
knowttl merged 17 commits into
mainfrom
fm/fm-upstream-sync-10

Conversation

@knowttl

@knowttl knowttl commented Aug 14, 2026

Copy link
Copy Markdown
Owner

sync: merge upstream kunchenguid/firstmate main (16 commits)

Absorbs the 16 commits the fork was behind (merge base 07450b9) while
preserving every fork-side change. Eight files conflicted; each resolution
below is backed by the per-file evidence commands from the /firstmate-sync
skill, not by preference.

Absorbed upstream commits

The Cursor primary and crew harness, and the when condition->action watcher,
are new surfaces that auto-merged clean and are absorbed wholesale.

Conflict resolutions

atelier vs lavish (fork PR #25, captain's decision 2026-07-13)

The fork renamed lavish-axi to atelier-axi everywhere; upstream still uses
the old name. The fork naming is kept through every conflict. Upstream's new
prose reintroduced the old name in .agents/skills/process-event-sources/, so
that skill takes upstream's content (the when watcher sections and the
condition->action eligibility boundary, kept in full) with Lavish ->
Atelier, bin/fm-procevent-lavish.sh -> bin/fm-procevent-atelier.sh, and
lavish-axi -> atelier-axi restored. git grep -i lavish over the whole
tree returns nothing, and bin/fm-procevent-atelier.sh is the only adapter
script for that surface.

docs/verification/process-event-sources.md takes upstream's Scope paragraph,
which drops the "Lavish is the first adapter" sentence entirely in favour of
generic adapter prose plus the when adapter's registration-publisher note, so
no rename was needed there. The fork's re-verification evidence for
atelier-axi 0.3.3 elsewhere in that file is untouched.

AGENTS.md state-layout list - union

Keeps upstream's new .cursor-park-owner line, keeps .wedge-verified-* in
the watcher-internals line (fork feature upstream lacks, see below), and drops
the fork's .drain-cursor-<id> / .drain-retry-* line together with
.wake-annotation.lock because their machinery is superseded (see below).
AGENTS.md now diverges from upstream only by the three intentional fork
changes: the per-secondmate harness pin, .wedge-verified-*, and the atelier
naming.

bin/fm-classify-lib.sh - upstream supersedes fork #21

Fork PR #21 taught _fm_decision_key to accept a [key=<slug>] token leading
the note. Upstream kunchenguid#2202/kunchenguid#2280 implement the same behavior as a strict
superset: _fm_key_at_note_head accepts the same position, a token deeper in
the note stays prose, an invalid slug is rejected rather than folded to
default, and upstream additionally strips a consumed note-head token from the
note and ends verb parsing at the first [name=value] tag instead of
special-casing [key=. Upstream is adopted for both hunks.

Verified rather than assumed: the fork's own three regression tests for #21
(test_key_after_the_colon_files_under_its_real_key,
test_distinct_after_colon_decisions_do_not_collapse,
test_malformed_key_token_opens_no_decision) survive in
tests/fm-wake-drain-open-decisions.test.sh and all pass against upstream's
parser. Fork #21 is superseded, not lost.

The fork's wedge_escalation_deferred and
FM_WEDGE_WORKING_ESCALATE_SECS_DEFAULT (PRs #14/#18) sit outside the
conflicted hunks and are preserved unchanged.

bin/fm-wake-lib.sh and bin/fm-wake-drain.sh - upstream supersedes fork #9

This was the incompatible-direction risk, and the evidence resolves it as
supersession rather than feature loss.

Fork PR #9 added a home-private per-status-file byte cursor
(state/.drain-cursor-<id>), bounded backlog output with [truncated] and
"N annotations omitted" markers, durable retry markers
(.drain-retry-{direct,historical}-<id>), a .wake-annotation.lock, and a
fm_wake_print_annotations '' call on the empty-queue drain path, so that a
wake coalescing several status appends could not hide an earlier event.

Upstream kunchenguid#2331 solves the same problem with a fleet-wide presentation cursor
manifest (state/.status-presentation-cursor) owned by fm-classify-lib.sh and
a snapshot/acknowledge/commit protocol (print_status_presentation). It is
strictly stronger on every axis the fork's version covered:

  • it prints every still-unread line rather than a capped-and-deferred subset,
    so there is no remainder left to retry (the byte caps the fork bounded were
    upstream's own pre-existing code, which fix(bin): surface buried wake status lines once kunchenguid/firstmate#2331 deliberately removed);
  • its cursor is committed transactionally against the presented snapshot and
    bound to file identity, so rotation and a reused task id are handled;
  • print_unread_status_section runs on every drain including the empty-queue
    fast path, which is what the fork's extra empty-queue call existed to do;
  • teardown retirement is integrated through
    status_retire_presentation_task.

Keeping both would leave two competing sources of truth for "already
presented", which is exactly the one-owner violation the repo's coding
guidelines forbid, and the two cursors would disagree because the fork's
advances at print time while upstream's commits on acknowledgement. Upstream is
therefore taken wholesale for both files, and the orphaned fork artifacts are
removed with it. Each file's only fork divergence was #9, confirmed with
git log <merge-base>..main -- <file>.

Upstream's own tests assert the preserved behavior directly:
test_signal_annotation_surfaces_every_unread_note_not_only_the_newest and
test_unread_output_over_cap_remains_recoverable in
tests/fm-wake-drain-unread-status.test.sh. tests/fm-wake-queue.test.sh
likewise takes upstream, dropping the six fork tests that exercised the removed
cursor/retry internals; their behavior is covered by the suites above.

bin/fm-teardown.sh

Resolves to upstream, which is now byte-identical. Upstream's
status_retire_presentation_task removes both <id>.status and
.<id>.open-decisions-cursor, superseding the fork's explicit cleanup of the
latter, and adds <id>.cursor-session for the new Cursor harness. The fork's
.drain-cursor-* / .drain-retry-* cleanup is removed because that state no
longer exists.

Fork features confirmed still present

Verification

  • bin/fm-lint.sh: clean, ShellCheck 0.11.0 (pinned 0.11.0).
  • bin/fm-doc-audience-check.sh: ok, surfaces=70 local_links=259.
  • 18 suites run green, covering every conflicted subsystem plus the new
    upstream surfaces: fm-wake-drain-open-decisions, fm-classify-decision-key,
    fm-wake-drain-unread-status, fm-wake-drain-open-decisions-cursor,
    fm-wake-queue, fm-cursor-harness, fm-cursor-primary, fm-procevent-when,
    fm-procevent, fm-guard-stale-banner, fm-tmux-agent-liveness,
    fm-decision-hold-lifecycle, fm-send-resolve-key, fm-watch-triage, fm-daemon,
    fm-teardown, fm-teardown-endpoint-safety.
  • tests/fm-session-start.test.sh fails one case here
    ("MISSING diagnostic did not appear at all"). This is not a merge
    regression: a pristine checkout of upstream/main fails the identical case
    with the identical message in this same environment. The tools the fixture
    expects to resolve (tasks-axi, quota-axi) live outside the test's isolated
    BASE_PATH on this machine. Left for CI rather than worked around.

kunchenguid and others added 17 commits August 11, 2026 12:35
* fix(session-start): refresh drifted instructions on stale rebuilds

* test(session-start): prove Pi instruction refresh end to end

* no-mistakes(review): Fix stale instruction refresh and baseline integrity

* no-mistakes(review): Preserve true-start baselines across Pi continuations

* no-mistakes(review): Correct Pi continuation classification and live expectation

* no-mistakes(review): Correct Pi continuation coverage documentation

* no-mistakes(review): Fix read-only refresh and exact Pi session restores

* no-mistakes(review): Classify Pi create-if-missing sessions correctly

* no-mistakes(review): Classify named Pi sessions using immutable headers

* no-mistakes(review): Correct Codex interactive coverage diagnostic

* no-mistakes(document): Document immutable Pi compaction instruction refresh

* no-mistakes(document): Correct Pi refresh documentation and validation claims
* feat(bin): add deterministic condition->action watch adapter on the process-event channel

Register a (condition, action) pair once with bin/fm-procevent-when.sh and the
existing process-to-event runner polls the condition tokenlessly, fires the
action at most once on a stable true, and wakes firstmate exactly once with the
captured outcome - instead of burning an agent turn per re-check.

The pair is stored privately under state/when/ and hash-bound by a trust record
the same way fm-check-register.sh binds a custom check, so a mutated spec is
refused without executing anything. A durable exclusive fired marker claimed
before the action makes restarts and re-polls unable to double-fire; every
failure path (mutated spec, condition error past budget, expired deadline,
failed action, uncaptured earlier fire) ends in a terminal captured outcome
that wakes firstmate rather than a silent retry. Eligibility stays a firstmate
judgment: only exact, safe, reversible actions may be bound, and judgment-
needing or destructive actions keep the wake-and-decide flow.

* no-mistakes(review): Harden when watcher concurrency, deadlines, timeouts, and output

* no-mistakes(test): Bind watcher actions to registered executable bytes

* no-mistakes(document): Correct condition-action watcher documentation

* no-mistakes(document): Clarify outcome wake re-announcement

* no-mistakes: apply CI fixes
…id#2202)

The open-decisions fold only recognized a [key=<slug>] token between the
verb and the colon (needs-decision [key=x]: note). The common worker
shape with the colon first (needs-decision: [key=x] note) silently
folded its stated key into the shared "default" bucket, so two open
decisions could collapse into one record and fm-send --resolve-key <x>
refused to close the decision it plainly named.

A complete token at the head of the note is now an equivalent stated-key
position for every keyed verb, shared by the whole-file and incremental
folds through the one _fm_decision_key owner. The documented
before-colon position wins when both are present, a token deeper in the
note stays prose, a bare keyless line still folds to "default", and a
stated-but-malformed slug is rejected rather than rewritten to
"default". A consumed note-head token is stripped from the note so both
positions yield identical records, and the incremental fold version is
bumped so persisted cursors folded under the old interpretation are
rebuilt from the authoritative log.

Fixes kunchenguid#2109
…uid#2212)

* fix(bin): keep a recovery acknowledgement valid across republication

A watcher cycle that opened and closed while the model handled its drained
wakes minted a fresh recovery generation, which invalidated the exact
acknowledgement the drain had just printed. That acknowledgement then consumed
nothing, so the marker stayed pending and every later arm spent its whole cycle
re-announcing the same recovery instead of supervising - a livelock the home
could not leave on its own.

A downtime publication now reuses the generation of an outstanding handling
episode, so a close during the handling window cannot orphan the printed
acknowledgement. The acknowledgement itself separates its two facts: queue-row
consumption is bound to the monotonic --ack-through sequence and always
happens, while only retiring the episode is bound to --recovery-generation. A
generation that moved on is a non-fatal result that names its own remedy
instead of a refusal that consumes nothing.

* no-mistakes(review): Preserve recovery generations and consume stale acknowledgements safely

* no-mistakes(document): Document sequence-bound recovery acknowledgements
* feat(fmx-respond): consume in_reply_to_chain conversation context

The relay's poll payload can carry in_reply_to_chain, an oldest-first
transcript of the surrounding conversation, but the mention-handling
procedure only ever read the immediate in_reply_to parent, so referents
like "this" in a standalone mention stayed unresolvable even when
context was delivered.

Teach fmx-respond to read the chain when present (optional and
backward-compatible: often absent today, kind label not required),
resolve referents against the whole transcript, and extend the
untrusted-content framing to every chain entry including the upcoming
kind=history entries. Document the field's wire shape in
docs/configuration.md as the firstmate-side owner.

* no-mistakes(document): Document Relay chain context ownership
* fix(bin): strip every bracket tag, not just [key=...], from a status verb

status_line_verb only stripped a leading "[key=...]" token before the
colon, so a remote secondmate reply's leading "[corr=...]" correlation
tag stayed glued onto the returned verb word ("needs-decision
[corr=...]" instead of "needs-decision"). The open-decisions fold's
verb match then silently failed to recognize the line at all, so
fm-send --resolve-key refused to close a decision that was plainly
open on the status line.

Generalize the parser to strip every "[name=value]" tag before the
colon, in any order and count, so local and remote replies fold
identically.

* no-mistakes(review): Invalidate stale decision cursors after parser fix

* no-mistakes(document): Clarify status metadata verb parsing
* fix: collapse duplicate supervision wakes without losing legitimate updates

One remote-secondmate note produced two handling turns (a procevent check
wake published before autohandle, then a signal wake for the same mirrored
bytes), already-ingested replays such as a cursor-loss whole-log recapture
still woke with nothing to do, this home's own bookkeeping closes (fm-send
--resolve-key, the pending-reply escalation close, the captain-held
transfer) re-woke the session that wrote them, and turn-ended-only wakes
were annotated with already-announced status lines that looked like fresh
progress.

Dedup rules, each at its layer's one owner:
- fm-procevent.sh: an adapter may declare 'self-announcing'; the runner
  then applies first and publishes a check wake only for what remains
  unhandled. fm-procevent-remote-reply.sh declares it: the mirrored status
  append is the single announcement, so a fully applied capture publishes
  nothing and a byte-identical replay stays completely quiet. All other
  adapters keep strict publish-before-apply.
- fm-wake-lib.sh: fm_wake_signal_sig/seen_path/seen_current now own the
  watcher's signal signature and .seen-* marker format, plus
  fm_wake_status_append_self_announced, the guarded bookkeeping append
  that advances the marker only over exactly its own bytes and fails
  toward waking on any pending or interleaved foreign write.
- fm-send.sh, fm-pending-reply-lib.sh, fm-decision-hold.sh: bookkeeping
  closes go through that guarded append; escalation opens stay plain
  appends because a new blocker must wake.
- fm-wake-lib.sh annotations: a historical (turn-ended-only) row skips its
  status annotation only when the file's signature provably matches the
  seen marker; anything unannounced keeps annotating.
- fm-classify-lib.sh: a kind=secondmate task's status signal is never
  absorbed as provably-working, because that stream is the routed-reply
  channel the parent must read.

Also fixes a pre-existing exit-path deadlock the regression run reproduced:
a TERM inside a recovery-marker critical section left fm_lock_try_acquire
spinning against this same process's abandoned hold; a self-held lock is
now reclaimed (a subshell still waits on its parent's live hold).

Regression tests drive the real wake functions and executables in both
directions: each duplicate case collapses, while a new remote reply, new
decision, new blocker, merge result, failure, first status change, and a
later different note on the same task all still wake.

* no-mistakes(document): Document wake deduplication contracts
* feat(harness): add Cursor Agent CLI adapter

# Conflicts:
#	bin/fm-spawn.sh

* fix(composer): read cursor-agent's reverse-video placeholder as idle

cursor-agent renders its idle composer placeholder dim (SGR 2) but paints the
cell under the terminal cursor in reverse video (SGR 0;7). Reverse video is
neither dim nor a dark truecolor foreground, so the shared ghost stripper keeps
that one character and an idle composer reduces to a lone `P`. Judged on its
own, that remnant reads `pending` on a genuinely idle pane, which defers
away-mode escalation indefinitely on the styled cursorless backends.

Teach the ONE fleet-wide classifier the shape instead of adding an adapter-local
copy: register `→` as an agent prompt glyph so the composer row is structurally
findable at all (without it the bottom-most shape is a stale shell prompt echo
in the scrollback), add both verified placeholders to the idle set, and consult
the styling-independent plain row when the styled row is only a remnant.

The plain-row branch demands the remnant be a proper, strictly shorter substring
of a plain row matching a fully anchored placeholder. Real typed text is
uniformly bright, so stripping leaves it equal to the plain row and it stays
`pending` - verified live against a pane where the typed text was exactly the
placeholder string.

Verified live on cursor-agent 2026.08.11-e8db854; the regression pins the real
captured bytes and asserts the remnant survives stripping, so the case cannot go
vacuous if the stripper later learns SGR 7.

Co-authored-by: Amplify Logic AI <lars@sockinator.co>

* feat(cursor): narrow cursor identity and order its marker before CLAUDECODE

Cursor ships two executable names - `cursor-agent` and the legacy alias `agent`
- and runs as a bundled node script, so tmux reports the pane command as a bare
`node`. Neither `agent` nor `node` can be trusted by name, so identity gets one
owner in bin/fm-cursor-lib.sh that demands cursor's own name or install tree in
the path or argv[0], from the structural signal only. Probing an arbitrary pid's
executable during a liveness poll would execute a stranger's binary, which is
the hazard that rule exists to close.

Two consequences wired up:

Detection. cursor-agent does NOT clear an inherited CLAUDECODE, so a cursor
worker launched under a claude primary carries both markers and whichever is
tested first wins. The cursor markers are ordered ahead of the CLAUDECODE check;
fm-spawn additionally clears foreign markers at the launch boundary. Both are
kept deliberately - launch sanitization only covers sessions fm-spawn started,
while the ordering also covers a cursor session started by hand. Verified live
that CURSOR_INVOKED_AS is set on the agent process and CURSOR_AGENT=1 on the
child/tool processes fm-harness.sh actually runs as.

Pane liveness. A cursor pane now classifies `agent`. An unrelated node or agent
stays `other`, which the liveness callers already fold into `ambiguous` rather
than `dead`, so a stranger's node pane is never reported agent-free.

Resolution prints the STABLE launcher rather than the canonical target: identity
is proven through canonicalization, but cursor's canonical path carries a
version its own auto-update replaces, and pinning that would strand a task on a
version that can vanish.

The regression drives the two identity signals apart - a cursor-named executable
outside any cursor tree, and a non-cursor-named alias inside one - and asserts
each carries a verdict alone, so no single vendor string is load-bearing. Its
negative controls are real spawned processes, not fixtures.

Verified live on cursor-agent 2026.08.11-e8db854.

Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>

* feat(cursor): classify cursor busy state from its own turn transcript

Cursor shipped as "unknown cursor-unverified" on the premise that it exposes no
semantic turn lifecycle, only a rendered "Working" footer. That premise is
wrong: cursor-agent persists an append-only JSONL transcript per conversation
and brackets every submitted turn with a role:user open and a typed turn_ended
close. Verified live on 2026.08.11-e8db854, including the interrupt path, where
Escape closes the turn with status "aborted" - so this source covers manual
interruption, which Claude's Stop hook does not.

That makes it a genuine pull source in the muse mould rather than the rendered
text the redesign forbids: no writer, no arm, no gen, nothing seeded that could
never be cleared. Cursor's `ctrl+c to stop` footer stays out of the verdict, and
herdr's narrower native streaming state cannot stand in for it either.

Binding deliberately does not reconstruct cursor's workspace-slug directory
name. That slug collapses path separators, so rebuilding it would be a guess
that could bind the wrong pane; cursor records the exact absolute workspace path
in each project's .workspace-trusted, and the binding matches on that. A
conversation recorded as prior at spawn is excluded, so a relaunch in a reused
worktree folds its own turn rather than its predecessor's. Requiring a unique
remaining conversation keeps zero and several both unknown, because neither
proves anything about the current turn.

The regression pins the fold with real transcript files and asserts the
dangerous direction stays closed: an unresolvable binding, a record-free file,
an unclaimed workspace, and a workspace-path PREFIX all read unknown, never
idle. The prefix case uses an opaque fixture slug so a slug-rebuilding
implementation cannot pass it.

Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>

* feat(cursor): make the cursor launch runnable and give it lifecycle control

Five gaps that together kept a cursor crewmate from being drivable end to end.

Launch. The template invoked `cursor agent`, but `cursor` is not the CLI - the
installed names are `cursor-agent` and the legacy alias `agent` - so the command
could not run at all on a machine with a normal cursor install. It now resolves
through the verified owner, which also refuses a spawn loudly instead of leaving
a pane that dies with command-not-found and reads as a wedged worker.

Session binding. fm-spawn writes state/<id>.cursor-session so the busy fold can
find this pane's transcript, and teardown removes it.

Lifecycle control. No cursor PR touched fm-control-lib.sh, so
`fm-control <id> interrupt|exit|relaunch` could not drive a cursor worker at
all. Verified live: interrupt is a single Escape, exit is /exit, and cursor does
NOT repollute its composer with the cancelled prompt, so unlike muse it needs no
clear key. Secondmate is refused, matching the spawn refusal.

Submit acknowledgement. cursor parks its terminal cursor outside its composer,
so the composer verdict on tmux is always `unknown` and a submit could never be
acknowledged from the composer alone. The submit core's existing idle-to-busy
transition covers that case, but only if the pane's busy footer is recognised,
so cursor's `ctrl+c to stop` joins the harness-less default union the submit
cores read. The TOKEN is matched rather than the spinner verb: the same version
rendered both `Working` and `Running` in consecutive turns.

Bootstrap. A configured cursor crew harness with no cursor executable is now a
loud MISSING diagnostic rather than a first-spawn failure, and it accepts either
installed name.

Interrupt cancellation is deliberately left unconfirmed. The transcript does
type an aborted close, but its post-interrupt write latency measured as
variable - sometimes seconds, sometimes not within twenty - so a claim built on
it would be unreliable. Normal turn completion is prompt, which is what the busy
fold actually depends on.

Two inherited tests are corrected rather than deleted: the busy test asserted
cursor could have no semantic source, and the launch test pinned the literal
`cursor agent` string. Both now pin the verified behaviour, including that the
launch never allocates a second worktree.

Co-authored-by: ABHISHAKE KUMAR BOJJA <abojja@uvic.ca>
Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>

* docs(cursor): record the verified crewmate facts and extend the drift guard

The inherited cursor entry was written against 2026.08.04-aaa8809 and several of
its claims no longer hold: it named `cursor agent` as the binary (not the CLI
name), listed six Grok model ids of which the live catalog now returns two, and
recorded busy state, exit, interrupt, and skill invocation as unverified.

Replaced with what was measured against 2026.08.11-e8db854, including the two
facts most likely to be rediscovered painfully: cursor runs as a bundled node
script so its pane title is a bare `node`, and it parks its terminal cursor
outside its composer, which makes the tmux composer verdict permanently
`unknown` by design rather than a defect to chase.

Model ids now route to `--list-models` for the account instead of a fixed list,
since that list is exactly what drifted.

The live drift guard covers cursor, resolving it through the same verified owner
fm-spawn uses and passing --trust so the probe cannot hang on the workspace
prompt. Run against every installed harness: 8 checked, all alive, with cursor
reporting title='node' foreground=[.../cursor-agent] - the drift shape this
guard exists to catch.

Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>

* docs(agents): record the cursor session-binding state file

The state/ layout section is the inventory every session reads; a busy-source
binding that fm-spawn writes and teardown removes belongs in it alongside muse's.

* no-mistakes(review): Sanitize ambient Cursor marker in harness tests

* no-mistakes(review): Validate Cursor models against live catalog

* no-mistakes(review): Reject unsupported secondmates before binary preflight

* no-mistakes(review): Narrow Cursor ancestry detection to structured process identity

* no-mistakes(review): Parse Cursor transcripts and sanitize inherited markers

* no-mistakes(review): Handle malformed Cursor transcript records safely

* no-mistakes(review): Validate malformed Cursor closes in fallback parser

* no-mistakes(review): Retire stale Cursor bindings during relaunch

* no-mistakes(review): Fix Cursor drift guard command variable

* no-mistakes(review): Narrow Cursor identity to versioned install trees

* no-mistakes(document): Document Cursor harness boundaries

* refactor(composer): move the delivery busy footers to the shared owner

The per-harness rendered busy footers lived in bin/fm-tmux-lib.sh under
FM_TMUX_* names, so cursor's `ctrl+c to stop` signature - and every other
harness's - was reachable only from tmux. That placement was wrong on its own
terms: herdr, zellij, cmux, and orca run the same harnesses and face the same
question these footers answer, which is whether a submitted Enter actually
landed. Nothing about the signature is tmux-specific.

Moved verbatim into bin/fm-composer-lib.sh, the shared composer/delivery owner
every backend already sources, and renamed to FM_DELIVERY_* so the names stop
claiming a scope they never had. All five adapters now reach cursor's signature;
verified per adapter rather than assumed.

The boundary the move must not blur is stated where it now lives: this is a
DELIVERY guard, never a worker-state source. Confirming a keystroke landed is a
different question from asking what a worker is doing, and bin/fm-busy-lib.sh
remains the semantic owner that forbids classifying a harness from rendered
text. Cursor still classifies only from its transcript fold, which is already
backend-agnostic because it folds a file rather than reading a pane - the same
verdict on all six backends.

The old FM_TMUX_* aliases are dropped rather than kept as dead shims: nothing
outside the moved block referenced them except fm-busy-lib.sh's grok fallback,
which now reads the new name. The documented operator override, FM_BUSY_REGEX,
is untouched.

Also removes a dead duplicate CURSOR_INVOKED_AS check in bin/fm-harness.sh,
unreachable behind the marker check above it.

* no-mistakes(review): Correct shared delivery guard ownership references

* no-mistakes(document): Document shared delivery guards and Cursor backend limits

* no-mistakes: apply CI fixes

* fix(composer): bound a bare composer's wrap region at a half-block rule

A live cursor crewmate on herdr classified its IDLE composer as `pending`, and
fm-send consequently exited 1 with "delivery unconfirmed" on a message that had
actually landed. The cause is not cursor-specific.

Herdr draws a composer's top and bottom rules with the half-block glyphs U+2584
and U+2580 rather than the box-drawing family. fm_composer_row_has_edge knew
only the box-drawing set, so no box was detected; the composer was found as a
BARE row, and its wrap region - which extends while rows are non-blank and carry
no structural edge - walked straight through the composer's own closing rule and
swallowed the model and path footer below it. That footer is real text, so the
region classified pending on a genuinely idle pane.

Teaching the shared edge detector the half-block glyphs bounds the region at the
closing rule. Measured on the captured bytes of a real herdr cursor pane: the
same capture that read `pending` now reads `empty`.

This is a shared shape-path change, so it is deliberately narrow - it adds
glyphs to the edge vocabulary and changes no verdict logic - and the whole
composer and backend suite is green, including the other harnesses' herdr
fixtures.

The regression pins the real captured shape and asserts the footer content is
genuinely present, so the case cannot pass vacuously if the region were ever
bounded for some unrelated reason.

* fix(herdr): confirm a cursor submit from the rendered-footer transition

Herdr's composer-shape fix made an idle cursor pane classify `empty`, but
`fm-send` still exited 1 with "delivery unconfirmed" on messages that had
actually landed. Live measurement found the second, independent cause.

Herdr reports a cursor pane `agent_status=blocked` in EVERY state - idle,
mid-turn, and after - so the submit path's idle-baseline native confirmation is
structurally unreachable for cursor and every send falls into the composer
branch. That branch reads cursor's mid-turn composer row, which renders its own
`Add a follow-up` placeholder beside a right-aligned `ctrl+c to stop`. That
token is composer content, so the verdict is `pending` on a composer holding no
user text at all, and the Enter-retry budget then reports pending.

The escape is the same semantic signal the native path uses, read from the
pane's verified busy footer instead of native agent-state, and it is the
rendered-footer twin of the tmux submit core's turn-started confirmation: an
idle-to-busy transition ACROSS our Enter proves the harness accepted the
submission. The baseline is taken before the first Enter and only when the
native baseline was not legibly idle, so the idle-baseline path still never
reads pane content and a pane already mid-turn before we typed keeps reporting
`pending` rather than borrowing another turn as proof of this delivery.

The composer verdict is deliberately NOT relaxed. A right-aligned status token
on the composer row stays content for every other caller, including the
away-mode pre-injection guard, and the shared cursorless submit core is left
untouched so zellij, cmux, and Orca keep the behavior their own follow-up owns.

Verified live on herdr 0.8.0 and cursor-agent 2026.08.11-e8db854 in an isolated
lab session: `fm-send` now exits 0 and the steer executes, interrupt cancels a
running turn, `/exit` stops the agent, and teardown clears the record. All seven
panes of the running default session classify identically before and after the
shape fix, so no other harness regressed.

* no-mistakes(review): Prevent working Herdr baselines from falsely confirming delivery

* no-mistakes(document): Correct Cursor harness and backend documentation

---------

Co-authored-by: ABHISHAKE KUMAR BOJJA <abojja@uvic.ca>
Co-authored-by: Amplify Logic AI <lars@sockinator.co>
Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>
* fix: raise quota-axi floor to 0.1.25 for Cursor CLI quota awareness

Homes on latest main need quota-axi kunchenguid#87 so Desktop-absent CLI machines report a fresh Cursor quota instead of a false sign-in-required.

* no-mistakes(document): Update quota floor documentation pointer
…id#2304)

* fix(guard): stop the false send-time watcher-down alarm on Pi primaries

On a Pi primary the watcher process is not the liveness signal. The Pi
extension tears the watcher down on every actionable wake and spawns the
replacement itself, so the singleton lock is legitimately unheld between
cycles: every one of the 799 cycles in a live primary's ledger ends with
lock_after=pid:none, and a live capture caught the guard verdict flipping to
no-watcher during one hand-off with the beacon 63s old.

bin/fm-guard.sh classified Pi as a persistent-watcher harness, which demands a
live identity-matched lock holder at all times, so any guarded command landing
in a hand-off painted the full WATCHER DOWN - SUPERVISION IS OFF banner and
told firstmate to repair a cycle the extension already owns and is restoring.

Add an extension supervision model for pi and pi-signed. A live
identity-matched watcher stays the ordinary healthy state; an unheld lock is
healthy only while the beacon is fresh within grace AND a live Pi session
provably owns continuity - both primary extensions recorded in their state
markers at their current on-disk builds by the process named in state/.lock,
with that process still alive. Without that proof the banner fires exactly as
before, so an unloaded, version-drifted, or exited Pi session is loud
immediately and a cycle the extension never restores is loud once the beacon
passes grace. The queued-wake warning, the PID-strict turn-end guard, and
every other primary's detection are untouched.

Fold session-start's duplicate Pi marker predicate into the shared library so
the ownership contract has one owner.

* no-mistakes(review): Restrict Pi hand-off tolerance to unheld watcher locks

* no-mistakes(document): Document Pi watcher hand-off supervision
* feat(cursor): add Cursor Agent CLI primary hooks, park supervision, and session start

Register a tracked project-scope .cursor/hooks.json for Cursor's stop,
sessionStart, preCompact, and preToolUse steps.

bin/fm-turnend-guard-cursor.sh owns Cursor's turn boundary as a park: it
foregrounds the watcher arm, holds the boundary open until an actionable close,
and returns that wake as one follow-up. Exit 2 is a silent no-op on Cursor's
stop step, so the adapter never uses it. The follow-up loop is bounded twice,
by Cursor's own loop_limit and by the payload's loop_count.

bin/fm-sessionstart-cursor.sh delivers the digest as additional_context at
sessionStart, and stages it for the next turn boundary at preCompact, which
cannot inject context.

Cursor also loads the tracked Claude settings, so bin/fm-hook-host-lib.sh lets
each tracked Claude-shaped entrypoint stand down on a Cursor-delivered payload
rather than running every covered event twice.

bin/fm-tmux-lib.sh reclassifies a Cursor pane's composer cursorlessly, because
Cursor parks its terminal cursor outside the composer, which restores a genuine
composer-empty proof and unblocks away-mode escalation delivery.

* feat(cursor): make Cursor Agent CLI a verified primary harness

Resolve Cursor in the session-lock ancestry through bin/fm-cursor-lib.sh, which
a Cursor primary needs before it can hold its own home lock, and classify its
stop-hook park under the autoarm supervision model so the mid-turn pull guard
stops reporting a healthy between-turns watcher as down.

Read a Cursor pane's composer cursorlessly on tmux, gated on Cursor's own
structural process identity, which restores a genuine composer-empty proof and
lets away-mode escalations reach a Cursor primary with no daemon change.

Lift the secondmate refusals in bin/fm-spawn.sh and bin/fm-control-lib.sh now
that the supervision protocol exists and is recorded.

Cover the whole surface with a portable regression over real processes, an
opt-in live guard against the installed cursor-agent, and dated per-harness
evidence.

* docs(cursor): record Cursor as a verified primary across the owning surfaces

Update the turn-end guard, session-start, arm-seatbelt, cd-guard, watcher
continuity, architecture, configuration, README, and harness-adapters owners,
and add dated live evidence to the supervision and runtime-backend verification
records. Correct the recorded Cursor tmux composer verdict: the cursor-anchored
read is still blind, but the composite reader is no longer unknown.

Lift the remaining remote-secondmate refusal missed in the previous commit, and
add the new libs to the existing fixtures that copy a fixed dependency list.

* refactor(cursor): name the park's stand-down condition for both its causes

Also record that Cursor's preCompact firing itself is not yet live-verified,
while the static evidence that it cannot inject context, and the staging path
that follows from it, both are.

* test: give the pretool fixtures their new dependency and one lint owner

The cd-guard fixture copies a fixed dependency list and now needs the shared
hook-host predicate. Both pretool suites also asserted cleanliness with a bare
shellcheck call, a second and weaker copy of the lint definition that
bin/fm-lint.sh owns: it omits --external-sources, so it failed the moment these
checkers sourced a shared library. They now delegate to that owner.

* test: assert the cursor secondmate contract instead of its removed refusal

A cursor secondmate now launches, so the suite asserts what its park actually
needs: --trust so the home's project hooks load at all, its own home pinned as
the workspace, and the autoarm supervision model inherited across the launch.

* no-mistakes(review): Serialize Cursor wakes and bind staged context

* no-mistakes(review): Serialize Cursor context and nag state commits

* no-mistakes(review): Enforce Cursor ceiling before staged context delivery

* no-mistakes(review): Serialize Cursor claims and staged context

* no-mistakes(review): Serialize Cursor ownership and state commits

* no-mistakes(review): Protect Cursor context across session takeover

* no-mistakes(review): Preserve Cursor context across session takeover

* no-mistakes(review): Enforce owner-keyed Cursor staged context

* no-mistakes(review): Atomically claim Cursor follow-ups and staged context

* no-mistakes(review): Defer Cursor preCompact staging and simplify supersession

* no-mistakes(review): Serialize Cursor park commits and defer preCompact

* no-mistakes(review): Stop Cursor parks after session takeover

* no-mistakes(test): Route Cursor preCompact context through stop follow-up

* no-mistakes(document): Update Cursor primary documentation

* revert(cursor): cut preCompact staging from this change

Carrying a compaction digest across two concurrently running stop hooks kept
producing races that could deliver it twice or strand it indefinitely, and
closing them kept enlarging a critical section inside a hook Cursor awaits at
the turn boundary. Native preCompact firing was never observed either, so the
surface has no empirical basis yet.

Remove the adapter, its registration, its staged path in the park, and its
tests, and record the surface as deferred and uncovered alongside the Codex
interactive TUI. A regression now asserts preCompact stays unregistered so it
cannot return without its own design and evidence.

This change ships the proven core only: the turn-end follow-up park, the
run-tier session start, and away-mode delivery.

* no-mistakes(review): Correct Cursor park supersession documentation

* no-mistakes(document): Clarify Cursor run-tier verification ownership

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

---------

Co-authored-by: kunchenguid <kun-1@kunchenguid.com>
…id#2330)

* feat(bin): add unrouted close paths to the captain decision gate

A captain who declines a held decision leaves no follow-up work to route,
so `resolve` could not express that answer: it requires at least one
`--routed-to` task. The only way to close such a hold was a direct
`tasks-axi done`, which never writes the durable resolution record the
completion gate reads, so the originating investigation could no longer
pass `verify` and its cleanup stayed blocked.

Add two close paths that route no work:

- `decline` closes an actively held hold with a recorded captain decision
  and no routed task. It refuses while any task is still blocked by the
  hold, because releasing routed work without recording it is `resolve`'s
  job.
- `repair` records the missing resolution block on a hold that was already
  closed outside this script. It never reopens a hold and never clears a
  dependency edge, and it refuses a hold that is still actively held.

Both require a non-empty captain decision file and share `resolve`'s
digest-based retry identity, so an exact retry is idempotent while a
changed decision is rejected. The recorded body now also names which path
closed the hold, and each routed entry regains its own line.

The gate itself is unchanged: an unanswered decision still fails
completion and blocks teardown, and neither new path can close a hold
without the captain's recorded word.

* fix(bin): require captain-hold provenance before repairing a decision

`repair` checked only that the backlog item was kind captain and Done, so
an ordinary captain-kind task that was never held for the captain could be
closed, repaired, and then pass the completion gate.

tasks-axi keeps `hold_kind` through a close, so it is the surviving proof
that an identity really was a captain hold. Require it before writing the
resolution record, and cover the case in the gate regression.

* no-mistakes(document): Correct decision-hold lifecycle documentation
* fix(bin): surface buried status notes on wake drain

A note: answer immediately followed by a routine note was dropped because
annotations kept only the newest line and note: never enters OPEN DECISIONS.
Present every unread note and pending-reply resolution since the last drain
cursor, and annotate every unread line on a queued signal.

* no-mistakes(review): Fix unread status cursor races and overflow

* no-mistakes(review): Preserve cursors when status span reads fail

* no-mistakes(review): Make status presentation transactional under I/O failures

* no-mistakes(review): Simplify unread status cursor and presentation locking

* no-mistakes(review): Align cursor failure regressions with transactional presentation

* no-mistakes(review): Retire stale presentation cursors during task teardown

* no-mistakes(review): Preserve routine status until signal annotation

* no-mistakes(review): Correct unread status cap documentation

* no-mistakes(document): Document unread wake status presentation

* no-mistakes(lint): Fix wake surfacing ShellCheck warnings

* no-mistakes: apply CI fixes
* feat(calm): add a max presentation level that hides mid-turn working notes

Calm's home-local preference becomes a three-state level instead of a
boolean: "off" is stock Pi, "on" is today's Calm, and "max" is Calm plus
hiding the assistant text of messages the model did not end its response
with. `/calm max` selects it from any state, a plain `/calm` steps max
back to ordinary Calm and otherwise keeps the existing on/off cycle, and
any other argument keeps that cycle too.

`config/calm` now persists "max" as its own literal value, so a session
start, resume, fork, or reload restores the stored level rather than
treating it as unrecognized and dropping to off.

The hide rule keys on Pi's intrinsic per-message stopReason: "toolUse",
or "length" with tool calls present. Streaming ("pending") text is never
filtered, because suppressing it would also stop a genuine reply from
streaming. The existing assistant layout adapter filters the blocks out
of the same shallow presentation copy it already uses for collapsed
thinking, so the message, model context, session storage, /export, and
delivery are untouched and a hidden mid-turn row collapses to zero
height. The new "assistant-working-note" class keeps that choice in the
visibility policy owner, where ordinary Calm keeps it visible.

* no-mistakes(document): Clarify Calm max persistence and taxonomy
* feat(calm): make hiding mid-turn working notes the ordinary Calm state

Calm collapses back to the two-state on/off toggle it was before the max
presentation level, with max's hide rule promoted into ordinary Calm.
Calm on now hides mid-turn assistant working notes in addition to what it
already hid, and the /calm command parses no argument again.

The hide rule itself is unchanged: assistant text is removed from the
shallow presentation copy when the message's own stopReason is "toolUse",
or "length" with tool calls present. Streaming ("pending") text is never
filtered, so a genuine reply still streams. The message, model context,
session storage, /export, and delivery remain untouched.

config/calm persists only "on" and "off" again, but the reader still maps
a persisted "max" to on so a home upgraded from the removed level keeps
Calm on instead of dropping to off.

The mid-turn hide is now default behavior rather than an opt-in level, so
docs/calm.md documents it for users, docs/configuration.md records the
two written values plus the legacy max mapping, and the feasibility
taxonomy drops its level-scoped wording.

* no-mistakes(document): Document ordinary Calm working-note hiding
Absorbs the 16 commits the fork was behind (merge base 07450b9) while
preserving every fork-side change. Eight files conflicted; each resolution
below is backed by the per-file evidence commands from the /firstmate-sync
skill, not by preference.

## Absorbed upstream commits

- 1238402 chore: store no-mistakes test evidence in the repo (kunchenguid#2355)
- 9823ff8 feat(calm): hide mid-turn working notes by default (kunchenguid#2339)
- 88d0f2e feat: add max Calm presentation level (kunchenguid#2334)
- db0280f fix(bin): surface buried wake status lines once (kunchenguid#2331)
- 5521323 feat(bin): add decline and repair paths for decision holds (kunchenguid#2330)
- 81f7020 feat: support Cursor Agent CLI as a primary harness (kunchenguid#2305)
- 85cefa9 fix(bin): prevent false Pi watcher alarms during hand-offs (kunchenguid#2304)
- 96876db fix(bin): require quota-axi 0.1.25 (kunchenguid#2300)
- 4930d2c feat: add Cursor CLI crew harness (kunchenguid#2238)
- b0ad61e fix(bin): collapse duplicate supervision wakes (kunchenguid#2287)
- b91016f fix: parse decision verbs before status metadata tags (kunchenguid#2280)
- b5d430d feat(fmx-respond): consume Relay conversation chains (kunchenguid#2206)
- c42cfe0 fix(bin): prevent watcher recovery acknowledgement livelock (kunchenguid#2212)
- 614fae6 fix(bin): honor a decision key stated after the verb colon (kunchenguid#2202)
- 81ce6dc feat: add deterministic condition-to-action watcher (kunchenguid#2200)
- e8c7645 fix: refresh stale Pi instructions after compaction (kunchenguid#2163)

The Cursor primary and crew harness, and the `when` condition->action watcher,
are new surfaces that auto-merged clean and are absorbed wholesale.

## Conflict resolutions

### atelier vs lavish (fork PR #25, captain's decision 2026-07-13)

The fork renamed `lavish-axi` to `atelier-axi` everywhere; upstream still uses
the old name. The fork naming is kept through every conflict. Upstream's new
prose reintroduced the old name in `.agents/skills/process-event-sources/`, so
that skill takes upstream's content (the `when` watcher sections and the
condition->action eligibility boundary, kept in full) with `Lavish` ->
`Atelier`, `bin/fm-procevent-lavish.sh` -> `bin/fm-procevent-atelier.sh`, and
`lavish-axi` -> `atelier-axi` restored. `git grep -i lavish` over the whole
tree returns nothing, and `bin/fm-procevent-atelier.sh` is the only adapter
script for that surface.

`docs/verification/process-event-sources.md` takes upstream's Scope paragraph,
which drops the "Lavish is the first adapter" sentence entirely in favour of
generic adapter prose plus the `when` adapter's registration-publisher note, so
no rename was needed there. The fork's re-verification evidence for
`atelier-axi` 0.3.3 elsewhere in that file is untouched.

### AGENTS.md state-layout list - union

Keeps upstream's new `.cursor-park-owner` line, keeps `.wedge-verified-*` in
the watcher-internals line (fork feature upstream lacks, see below), and drops
the fork's `.drain-cursor-<id>` / `.drain-retry-*` line together with
`.wake-annotation.lock` because their machinery is superseded (see below).
AGENTS.md now diverges from upstream only by the three intentional fork
changes: the per-secondmate harness pin, `.wedge-verified-*`, and the atelier
naming.

### bin/fm-classify-lib.sh - upstream supersedes fork #21

Fork PR #21 taught `_fm_decision_key` to accept a `[key=<slug>]` token leading
the note. Upstream kunchenguid#2202/kunchenguid#2280 implement the same behavior as a strict
superset: `_fm_key_at_note_head` accepts the same position, a token deeper in
the note stays prose, an invalid slug is rejected rather than folded to
`default`, and upstream additionally strips a consumed note-head token from the
note and ends verb parsing at the first `[name=value]` tag instead of
special-casing `[key=`. Upstream is adopted for both hunks.

Verified rather than assumed: the fork's own three regression tests for #21
(`test_key_after_the_colon_files_under_its_real_key`,
`test_distinct_after_colon_decisions_do_not_collapse`,
`test_malformed_key_token_opens_no_decision`) survive in
`tests/fm-wake-drain-open-decisions.test.sh` and all pass against upstream's
parser. Fork #21 is superseded, not lost.

The fork's `wedge_escalation_deferred` and
`FM_WEDGE_WORKING_ESCALATE_SECS_DEFAULT` (PRs #14/#18) sit outside the
conflicted hunks and are preserved unchanged.

### bin/fm-wake-lib.sh and bin/fm-wake-drain.sh - upstream supersedes fork #9

This was the incompatible-direction risk, and the evidence resolves it as
supersession rather than feature loss.

Fork PR #9 added a home-private per-status-file byte cursor
(`state/.drain-cursor-<id>`), bounded backlog output with `[truncated]` and
"N annotations omitted" markers, durable retry markers
(`.drain-retry-{direct,historical}-<id>`), a `.wake-annotation.lock`, and a
`fm_wake_print_annotations ''` call on the empty-queue drain path, so that a
wake coalescing several status appends could not hide an earlier event.

Upstream kunchenguid#2331 solves the same problem with a fleet-wide presentation cursor
manifest (`state/.status-presentation-cursor`) owned by fm-classify-lib.sh and
a snapshot/acknowledge/commit protocol (`print_status_presentation`). It is
strictly stronger on every axis the fork's version covered:

- it prints every still-unread line rather than a capped-and-deferred subset,
  so there is no remainder left to retry (the byte caps the fork bounded were
  upstream's own pre-existing code, which kunchenguid#2331 deliberately removed);
- its cursor is committed transactionally against the presented snapshot and
  bound to file identity, so rotation and a reused task id are handled;
- `print_unread_status_section` runs on every drain including the empty-queue
  fast path, which is what the fork's extra empty-queue call existed to do;
- teardown retirement is integrated through
  `status_retire_presentation_task`.

Keeping both would leave two competing sources of truth for "already
presented", which is exactly the one-owner violation the repo's coding
guidelines forbid, and the two cursors would disagree because the fork's
advances at print time while upstream's commits on acknowledgement. Upstream is
therefore taken wholesale for both files, and the orphaned fork artifacts are
removed with it. Each file's only fork divergence was #9, confirmed with
`git log <merge-base>..main -- <file>`.

Upstream's own tests assert the preserved behavior directly:
`test_signal_annotation_surfaces_every_unread_note_not_only_the_newest` and
`test_unread_output_over_cap_remains_recoverable` in
`tests/fm-wake-drain-unread-status.test.sh`. `tests/fm-wake-queue.test.sh`
likewise takes upstream, dropping the six fork tests that exercised the removed
cursor/retry internals; their behavior is covered by the suites above.

### bin/fm-teardown.sh

Resolves to upstream, which is now byte-identical. Upstream's
`status_retire_presentation_task` removes both `<id>.status` and
`.<id>.open-decisions-cursor`, superseding the fork's explicit cleanup of the
latter, and adds `<id>.cursor-session` for the new Cursor harness. The fork's
`.drain-cursor-*` / `.drain-retry-*` cleanup is removed because that state no
longer exists.

## Fork features confirmed still present

- `.wedge-verified-*` stale-wedge deferral (PRs #14/#18): live in
  `bin/fm-watch.sh`, `bin/fm-supervise-daemon.sh`, and
  `wedge_escalation_deferred` in `bin/fm-classify-lib.sh`; covered by
  `tests/fm-watch-triage.test.sh` and `tests/fm-daemon.test.sh`, both green.
- atelier-axi naming (PR #25), including the `ATELIER_AXI_MIN` 0.3.3 floor in
  `bin/fm-bootstrap.sh`.
- per-secondmate harness and effort pin (PR #20).
- Superseded rather than lost, with the evidence above: fork #21 (decision-key
  near miss) and fork #9 (coalesced drain backlog).

## Verification

- `bin/fm-lint.sh`: clean, ShellCheck 0.11.0 (pinned 0.11.0).
- `bin/fm-doc-audience-check.sh`: ok, surfaces=70 local_links=259.
- 18 suites run green, covering every conflicted subsystem plus the new
  upstream surfaces: fm-wake-drain-open-decisions, fm-classify-decision-key,
  fm-wake-drain-unread-status, fm-wake-drain-open-decisions-cursor,
  fm-wake-queue, fm-cursor-harness, fm-cursor-primary, fm-procevent-when,
  fm-procevent, fm-guard-stale-banner, fm-tmux-agent-liveness,
  fm-decision-hold-lifecycle, fm-send-resolve-key, fm-watch-triage, fm-daemon,
  fm-teardown, fm-teardown-endpoint-safety.
- `tests/fm-session-start.test.sh` fails one case here
  ("MISSING diagnostic did not appear at all"). This is not a merge
  regression: a pristine checkout of upstream/main fails the identical case
  with the identical message in this same environment. The tools the fixture
  expects to resolve (tasks-axi, quota-axi) live outside the test's isolated
  BASE_PATH on this machine. Left for CI rather than worked around.
@knowttl

knowttl commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

CI result: 12 of 13 checks pass (lint, repo invariants, test coverage guard, all behavior suites portable + Herdr, macOS stock-bash snapshot, timing aggregate).

The one failure is PR must be raised via no-mistakes, which is a delivery-path gate rather than a defect in the change: this sync was dispatched as direct-PR, so the no-mistakes pipeline was deliberately not run. Every other gate the pipeline would have enforced is green here, plus locally: bin/fm-lint.sh clean on pinned ShellCheck 0.11.0, bin/fm-doc-audience-check.sh ok, and 18 suites run green across every conflicted subsystem and the new Cursor and when surfaces.

Note for whoever lands this: merging with a merge commit rather than a squash keeps the upstream ancestry that the next sync's merge-base calculation depends on.

@knowttl
knowttl merged commit 0ec6850 into main Aug 14, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants