Repository navigation
feat(bin): sync upstream firstmate through 8818a804 - #18
Merged
Merged
Conversation
…ollback (kunchenguid#7625) * no-mistakes(review): wedge cap: guard the cap-fire counter reset with rollback * no-mistakes(document): docs: repair shard-pack wording and wedge-cap marker shorthand * no-mistakes(review): docs: correct away-release terminal-row bound claim * no-mistakes(document): docs: cover third guarded cap-state write in watcher comment * no-mistakes(ci): Reweighted the packing hint for tests/fm-supervision-host.test.sh from the local 789123 ms to 1192918 ms, the one completed successful CI sample (run 39642852135, job 119276758966), and let the LPT packer rebalance. The guard was not weakened (PORTABLE_SERIAL_MAX_WEIGHT_MS=1200000, PORTABLE_SERIAL_SHARDS=9, coverage guard and packing-boundary check untouched) and nothing was hand-placed. New packed hint sums of the 9 serial shards (ms): 1=1192918 (1 script), 2=1139923 (18), 3=1139903 (30), 4=1139922 (30), 5=1139917 (30), 6=1139886 (29), 7=1139891 (30), 8=1139880 (29), 9=1139911 (31). Total 228 scripts = the serial lane exactly; max 1192918 < 1200000, so it fits. Packing guard: bin/fm-test-run.sh --check-coverage -> FM_TEST_COVERAGE ok total=268 parallel=24 parallel_max_ms=665545 parallel_imbalance_ms=2 parallel_unhinted=0 serial=228 serial_shards=9 serial_unhinted=15 serial_max_ms=1192918 serial_budget_ms=1200000 herdr=16, exit 0. tests/fm-test-run.test.sh passes both packing contracts: 'coverage guard bounds the unmeasured share and serial packing within twenty minutes' and 'serial packing accepts the exact budget and refuses one millisecond above it'. bin/fm-doc-audience-check.sh ok (surfaces=124 local_links=851). docs/fm-test-portable-shards.md now states the same 1192918 ms for that script, citing run 39642852135 (documented duration == packed weight), and adds the required honesty note in the same paragraph style: run 39765114423 cancelled this script alone in a shard at the 30-minute job cap and upstream run 39618934309 hangs identically, so the weight is a fit to the 20-minute packing target, not a cure, and the underlying slowness is an upstream defect this branch does not own. No other file, weight, test or doc line touched. Unrelated pre-existing failure, verified by git stash at base commit 5af4072: tests/fm-session-lock-ancestry.test.sh asserts 'the pty-host was not reparented to init after the daemon ended' and fails identically on the unmodified tree (sandbox pty behaviour); the --jobs family-admission assertion in the same suite fails only because of it. Not caused by this change
…nguid#6588) * fix(bin): restore inherited handling before main-only hand-back * Restored downtime so an earlier engine turn could not suppress a later main-owned close while leaving a healthy successor behind * Routed restoration failures through the existing Stop-hook alarm instead of silently discarding the hand-back * Added host-and-hook regressions plus attended and away idle-return controls to separate missing monitoring from status classification * no-mistakes(test): Assert ordered stale event in returned-driver controls * no-mistakes(document): Document main-only hand-back downtime restoration and failure * no-mistakes(document): Correct host header on main-only downtime republish
…unchenguid#7639) * feat(rex): push blocked edges from Rex program status through a bounded Lua reader Rex joins Herdr as a push-capable backend: a bounded rex do reader streams program_status_changed, child_exited, and block_closed for the watched task blocks, and a trusted blocked edge wakes the supervisor with Rex's blocked kind and message in the reason. The per-pane dedupe moves into the shared transition lib, the record gains a detail field, and gone edges end the wait for an immediate poll. The real-Rex test isolation is shared by both smokes. * fix(rex): anchor the composer read on Rex's cursor position Rex's format method reports the cursor alongside the styled grid, so the composer verdict now uses the same cursor-anchored read as tmux and falls back to the cursorless read only where that is unknown. OpenCode 1.19 draws a status line below its composer, which made every cursorless read of an idle OpenCode worker on Rex unknown and refused steers, exit, and relaunch. * test(rex): add a live composer guard for every installed harness on Rex * docs(rex): record push-event and per-tool live evidence on Rex * no-mistakes(document): Clarify Rex documentation and shared transition ownership * no-mistakes(ci): Fixed flaky public-followup fixture teardown: killing only the remote worker child allowed its supervisor to restart and race directory removal. Cleanup now uses the existing whole-worker-tree stop helper. Verified the full public-followup suite, remote-job orphan-reap suite, shell syntax, and git diff checks all pass
…nd an opt-in flag (kunchenguid#7634) * refactor(bin): share the branch-to-PR lookup through fm-pr-lib * feat(bin): notify owned PR health changes behind an opt-in flag * no-mistakes(review): drop status-line ownership, key checks per name, remove PR wait rule * no-mistakes(review): rediscover the branch PR once the cached one is final * no-mistakes(review): refuse retired re-enrollment, require marked checks, verify owned records * no-mistakes(document): note shared branch-to-PR lookup in scripts table * fix(bin): notify reported failures when no checks are required * no-mistakes(document): inventory PR health flag and state, note new pr-forge member
* Isolate real Rex tests with private servers * no-mistakes(review): Trap Rex cleanup early and prove passive-read autostart guard * no-mistakes(document): Consolidate Rex test isolation documentation
* fix(bin): report an outdated bootstrap tool instead of a missing one detect_local_tools printed "MISSING:" for a treehouse without --lease, a no-mistakes below 1.46.0, a gh-axi below its floor, and an incompatible tasks-axi or quota-axi, so a supervisor read an installed tool as absent and took the first-install remediation again. Report those as "OUTDATED:" with the installed version, the requirement and the tool's own upgrade command: treehouse update, no-mistakes update, gh-axi update and quota-axi update. tasks-axi ships no self-updater, so it keeps its install command. "MISSING:" stays for absent tools only, and lavish-axi keeps PRESENTATION_UNAVAILABLE with its text fallback. Update the bootstrap-diagnostics handler, the AGENTS.md section 13 trigger list and the operator diagnostics in docs/configuration.md, and cover absent, below-floor and at-floor for every floored tool in tests/fm-bootstrap.test.sh. * no-mistakes(document): Documented distinct bootstrap tool diagnostics * fix(bin): reuse the tasks-axi check's own probe results Rendering the tasks-axi OUTDATED requirement text ran the two `--help` capability probes again after the compatibility check had already run them, including when session start handed bootstrap an incompatible verdict. The check now records the reason behind its verdict - the version floor, or the capability probes the build fails - and reuses it, both within the process and across the existing one-hop handoff, so naming a missing capability costs no second probe pass. A build below the floor is answered by its version alone and never pays for capability probes it cannot use. The bootstrap tests that simulate a tool as absent removed it from the fake bin directory while bootstrap kept searching the fallback base PATH, so a real host install could satisfy the lookup instead of the expected MISSING line. Those cases now hide the tool from the base path as well, and a new case pins the contrast between a bare and a curated fallback. Verified: the touched bootstrap suite passes, and its probe-count and capability assertions fail against the pre-fix scripts. Run with a fallback base PATH carrying real installs of the absent tools, the pre-fix suite fails on "absent treehouse reports a missing tool" while the fixed suite passes every case. * no-mistakes(document): Documented missing versus outdated tool diagnostics * no-mistakes(document): Refresh outdated-tool documentation * no-mistakes(review): Reuse shared version parser for outdated diagnostics * no-mistakes(document): Correct outdated-tool diagnostics documentation * no-mistakes(document): Align toolchain upgrade documentation * fix(skills): keep main's bootstrap-diagnostics description and only add OUTDATED
…e verbatim match (kunchenguid#7530) * Clarify ahoy legacy startup exclusion * Fix quoted payload code span
…unchenguid#7637) * fix(timeout): bypass shell builtins when invoking external runners * no-mistakes(review): Remove redundant slashless runner resolution from external timeout
…urable (kunchenguid#6094) * fix: count answered captain holds in the Done archive at the completion gate Done retention moves closed markdown rows into the configured Done archive, which tasks-axi show never reads, so verify, complete, and scout teardown refused an answered captain hold once retention archived it. The completion gate now reads the newest archived block for an attested entry, under its exact or legacy identity, and accepts it only through a recorded resolution. Unanswered archived closes and holds found nowhere still refuse. * no-mistakes(document): document Done-archive durability in captain-hold verification record * no-mistakes(ci): Fixed the selected Greptile findings in bin/fm-captain-hold.sh (all in the Done-archive read path added by this PR). code_change_needed=true. ci-3 (Archived reads can hang): task_show_archived() now bounds the replayed archive `tasks-axi show` with the same read bound the live backlog read uses (FM_BACKLOG_ROW_TIMEOUT_SECS via fm_run_timed, sanitising a non-positive/padded value to 10s, mirroring fm_backlog_row_show). A bound hit is treated as 124, not absence: task_show_archived returns 124 and prints a loud reason; archived_entry() propagates 124; verify_entry_durable() exits 124 on the archived-read bound (alongside its existing resolve 124 handling); verify_hold_durable() exits 124 rather than reading the row as absent. So verify can no longer hang and complete can no longer stay blocked while holding the origin metadata lock. ci-4 (User-level archive setting ignored): added captain_markdown_archive_setting(), which resolves the [markdown] archive key with tasks-axi's own source precedence — the backlog root's .tasks.toml first, then $HOME/.tasks-axi/config.toml — before the existing default of done-archive.md beside the backlog; a relative value still resolves against the backlog root, which I verified empirically matches tasks-axi (root config wins over home config; a relative home-config value resolves against the backlog root; default stays data/done-archive.md). tasks-axi exposes no command for the archive path, so re-parsing with matched precedence is the available approach. Added test_archived_answer_in_a_user_configured_archive_satisfies_the_gate, which sets the archive only in a fixture $HOME/.tasks-axi/config.toml and drives the whole hold→answer→retention→verify/complete/teardown flow with that HOME, asserting the answered call archived to the relocated location still satisfies the gate. ci-5 (repeated archive scans, P2): left unchanged per the instruction's opt-out. A clean reuse of the row archived_entry already found is not small: archived_entry runs inside a command substitution, so its TASK_SHOW_OUTPUT dies with the subshell and cannot be handed to verify_hold_durable without restructuring both to pass the loaded row through the current shell. The growth is bounded (O(archive size) per attested entry) and P2, so hardening it would add more machinery than the fix warrants. Unanswered archived closes and holds found nowhere still refuse (existing test coverage retained). Verification: `bash -n` clean; pinned ShellCheck 0.11.0 (`shellcheck -x bin/fm-captain-hold.sh`) reports 0 findings; tasks-axi archive-precedence behaviour confirmed by direct experiment. The full fm-captain-hold-lifecycle.test.sh suite (which exercises both the pre-existing archived-gate test and the new user-config test) is slow due to beads spawns and was still running at report time; recommend confirming it green before merge. ci-1/ci-2 need no code change (action_required fork workflow runs awaiting maintainer approval), per the recorded user decision * no-mistakes(document): Document Done-archive reads under backlog row timeout * docs: say only the archive row replay is bounded, not the archive scan * no-mistakes(document): Move Done-archive rule to the complete section
…kunchenguid#7627) * fix(bin): optionally read a PR with no CI checks as unverified, not checks green nm_ci_checks_state maps the ci log marker "no CI checks reported - still monitoring" to green, the same verdict as "all CI checks passed". A pull request that no check ever ran against therefore surfaced as "checks green: PR ready for review" (kunchenguid#3216). Add a no-checks verdict for that marker, returned only in a home that creates config/ci-no-checks-not-green. The run-step consumer reports working with a detail naming the real condition, and the status-log override no longer promotes a crew's own "checks green" report while that marker is the latest. Without the flag every reading is unchanged. Co-authored-by: Harlan Do <hieudtr8@gmail.com> * no-mistakes(document): document ended-monitor reading under the zero-checks flag * fix(bin): read a PR with no CI checks as unverified on every home The ci log marker "no CI checks reported - still monitoring" now always yields the no-checks verdict, so a pull request that no check ran against is reported as working instead of checks green (kunchenguid#3216). The opt-in flag, its docs section, and its skill entry are removed, the default-path test now expects working, and the usage header and architecture note describe the no-checks reading. --------- Co-authored-by: Harlan Do <hieudtr8@gmail.com>
… test root-safe (kunchenguid#7650) * no-mistakes(review): correct the counter-reset log line, the sentinel comment, and the root proof Three findings the review step recorded but never applied, all in this branch's own change. The clean-rollback branch of the counter-reset guard logged "cap state rolled back", copied from the sibling marker-write branch. That is wrong here: the cap marker was already written before this block and _wedge_cap_rollback only truncates the counter, re-arms the since-file and clears write tracking - it never clears the markers. The window therefore stays silenced for FM_CAP_HORIZON_SECS, so the one operator-facing line must say that rather than claim the cap state was rolled back. The rollback behaviour is unchanged and no marker-clear call is added: the marker is deliberately kept so the window stays quiet. The sentinel short-circuit comment opened "from either route that has to defend cap state" and then enumerated three routes. Wording only. The new regression case injected its fault with chmod 444, which does not restrict root, so under root the guard would never fire and the case would fail on its own exit-code assertion. It now skips as root the way this suite's other mode-based faults do. The assertions are untouched: as the ordinary user the case still proves the guard. * no-mistakes(review): name both ways a capped window leaves the silence in the reset log line The tier-0 message for a refused post-fire escalation-counter reset says the cap marker stands, so the window stays silent until its horizon elapses. That names one release route where there are two: wedge_episode_end drops the marker through _wedge_cap_markers_clear, so any episode-ending route ends the silence early, and docs/configuration.md records deleting the marker as the operator's way to re-engage a capped window at once. This tier writes no sentinel, so the marker is the only thing left to remove. The message now names the horizon, an episode-ending route, and the operator remedy, in one line in the same shape and position as its sibling tier-0 messages. The rollback behaviour, the marker handling and the tier are unchanged. * no-mistakes(document): correct root-skip comment's mode-bit precedent in wedge-cap test
* fix(bin): resurface wakes appended while a handling successor is parked The supervision host parks on the handling successor it starts for a wake its engine handled, and a handling successor never resurfaced recovery, so a row another script appended during the park (a captain inbox note from the Basecamp integration) sat queued until an unrelated close - four hours in the observed case. A handling successor now records the newest queued sequence at its start and closes once with the recovery wake while a newer row is still queued; a newer row already consumed only advances that mark, so the predecessor's delivered episode is still never re-announced. Follow-ups, not built here: - A row appended in the short window between a handled close and its successor's start (about a second) is below the successor's mark; if the engine handles that close without claiming the row, the row waits for the next close. Closing that window needs the host to treat rows left queued after a handled attended turn as main's, which is a separate host change. - After a machine reboot the primary session does not restart on its own, so queued wakes wait until it is resumed by hand. Either auto-resume the primary session after boot, or alert the owner when the primary session is down while wakes are queued. * no-mistakes(review): Close handling-successor startup wake race * no-mistakes(test): Stabilize parked-host inbox wake regression timeout * no-mistakes(document): Clarify handling-successor wake continuity guarantees * no-mistakes(ci): Stabilized the flaky remote secondmate lifecycle test by extending the auto-relaunch watcher deadline from ~30 to ~60 seconds. The CI runner exceeded the old deadline by about 3 seconds; the changed production branch was not reachable in this fixture. `bash -n` and `git diff --check` pass. The full test was attempted but exceeded the 600-second local execution limit before reaching this case * no-mistakes(ci): Fixed flaky concurrent Herdr recovery testing by retrying only documented session-lock contention after both competitors finish. Cleanup now handles fixture-owned read-only hooks. Targeted E2E passed; bash syntax and diff checks passed. Shellcheck reported only existing SC1091 source-following notices * no-mistakes(test): Preserve successor wake boundaries across handoffs * no-mistakes(test): Revert successor boundaries and drain teardown fixture wake * no-mistakes(test): Notify main when downtime handback publication fails * no-mistakes(test): Revert downtime handback notification changes * no-mistakes(document): Correct stale supervision wake routing documentation * fix(bin): stop the supervision host deadlocking and re-announcing handed-back closes Two defects in this branch's handling-successor changes surfaced as the "Behavior portable serial 2" shard hanging at its 30-minute limit. A TERM that lands while the host settles a handled turn's successor handoff left the host holding the wake-queue and recovery-marker locks; its exit cleanup then waited forever on the queue lock it still owned. The cleanup now releases those locks if this process owns them before releasing its grant. When an accepted close turns main-only at its turn, the host hands it back and leaves the successor running, but republished the episode as pending downtime, so that successor re-announced the same close and exited. The handback now republishes the episode as already announced; a later durable append still opens a pending episode the successor recovers. * no-mistakes(test): Park on successor after handling settlement failure * no-mistakes(test): Use tab-delimited handled-settlement failure logs --------- Co-authored-by: Caleb LeNoir <calebl@users.noreply.github.com>
* fix(bin): keep accented letters in pending-reply request summaries fm_pending_reply_summarize deleted every byte outside printable ASCII, so a Portuguese summary lost its accents: "não faça a correção" was stored as "no faa a correo". It now removes control characters only. The 120-character cap counts UTF-8 characters from lead bytes, so it never splits a letter under any caller locale, including the C locale. * no-mistakes(document): Documentar preservação de acentos nos resumos --------- Co-authored-by: Alexandre Custodio <alexandre.custodio@soraschools.com>
…id#7149) * Add explicit OpenRouter Jev dispatch resolution * no-mistakes(document): Clarify dispatch documentation and remove stale duplicates * chore: exclude local Stele binding from PR * no-mistakes(document): Correct dispatch help and never-send documentation * ci: retrigger CI after a timed-out test shard * no-mistakes(document): Correct dispatch architecture documentation ownership
…imits (kunchenguid#6983) Opt-in: fm-watch.sh publishes state/lane-capacity.json via bin/fm-capacity.sh --publish only while config/lane-capacity exists; adds beat pressure and keyed limits to fm-capacity.sh. Unconfigured fm-capacity output unchanged.
…#5359) * fix(bin): transport contribution-input JSON through slurpfile, not argjson bin/fm-fleet-snapshot.sh --contribution-input passed the full backlog and task-record JSON to jq through --argjson. A single --argjson argument is capped by the kernel's MAX_ARG_STRLEN (131072 bytes on Linux), so a large enough backlog made the kernel refuse to exec jq while the script still exited 0 with a broken result. Transport both documents through temporary files and --slurpfile, matching the pattern the default snapshot path already uses, and propagate a non-zero exit when that jq invocation fails. Fixes kunchenguid#5349 * test: narrow contribution-input regression to final assembly failure The ARG_MAX transport is already fixed on main by kunchenguid#6830, so keep only the regression proving a failing final jq assembly exits non-zero.
* Fix Rex secondmate session lifecycle * no-mistakes(review): Retire Rex secondmate sessions under the owning home identity * no-mistakes(review): Limit Rex FM_ROOT label override to secondmate launches * no-mistakes(document): Clarify Rex secondmate grouping and lifecycle documentation
…gins, and exit (kunchenguid#7336) * feat(opencode): add OpenCode 2 support with version-gated launch and plugins OpenCode 2 dropped the v1 server-plugin hook API, so the five fm.primary.* plugins now export a default definition with an id and a setup function alongside their legacy OpenCode 1 server export, backed by a new shared lib/fm-opencode2.js helper; the turn-end guard, watch-arm, cd-check, pretool-check, and sessionstart-nudge install their behavior from setup(ctx) on OpenCode 2 and keep the server entrypoint for OpenCode 1. fm-spawn probes the resolved opencode executable's --version once and branches the launch, model flag, reasoning config, exit key, and busy-state plugin off the detected major. A parsed major >= 2 takes the OpenCode 2 shape (no --model, the model pinned through OPENCODE_CONFIG_CONTENT, a raw Ctrl-C exit, the session.execution.* busy plugin); OpenCode 1.x and any undetected or unparseable version fall back to the 1.x shape (--model, 1.x config, /exit) everywhere. The worker busy-state plugin is always provisioned, but OpenCode 1 keeps the pre-port session.status/session.idle plugin so its busy-state wiring is unchanged, and only a major >= 2 gets the session.execution plugin. Adapter and supervision docs, OpenCode 2 live fixtures, and regression tests for the unknown->1.x launch path and the version-gated busy-plugin shape are included. * no-mistakes(review): gate OpenCode 2 busy plugin turn-end touch on notifyTurnEnd * feat(opencode): launch detected OpenCode 2 workers with --standalone OpenCode 2's default launch attaches to the shared background service, which hosts plugins with the service's environment and ignores the pane's OPENCODE_CONFIG_CONTENT. A private server keeps per-worker model, permissions, FM_HOME, and lock ancestry correct. 1.x and undetected versions are unchanged. Ported from kunchenguid#5143. Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com> * fix(opencode): resolve the OpenCode 2 project location and treat every terminal turn as a turn end OpenCode 2.0.x hands plugins the project only as ctx.location.project.directory and takes ctx.session.prompt({ sessionID, text }), so the V2 setup path never found its root or delivered a prompt. Interrupted and failed turns now end a turn like a succeeded one, while a server shutdown does not. Ported from kunchenguid#5143. Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com> * test(opencode): add a token-free OpenCode 2 native plugin fixture to the live e2e A private opencode serve plus a loopback provider proves the five primary plugins load and act on OpenCode 2: startup nudge, cd and watcher-redirection guards, turn-end guard, watcher arming across succeeded, interrupted, and failed turns. The fixture declares the complete model capabilities object, which OpenCode 2.0.11 requires and 2.0.22 accepts. FM_OPENCODE_LIVE_BINS runs it across several OpenCode 2 executables. The credentialed half now picks its run flags and session tables by detected major, so it keeps working on 1.x. Fixture ported from kunchenguid#5143. Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com> * feat(opencode): send one Enter when an OpenCode 2 launch leaves the brief unsent OpenCode 2.0.11 prefills the --prompt brief without submitting it, while 2.0.22 submits it. After launch, fm-spawn waits for the worker's busy-state plugin to report the turn start, sends one Enter only if it has not started and the composer is not empty, and fails the spawn if the turn never starts. * docs(opencode): document the OpenCode 2 launch, delivery gate, and standalone primary homes Records the 2.0.11 and 2.0.22 live evidence for the native plugin fixture and the post-launch brief delivery gate. * no-mistakes(review): Trim OpenCode 2 adapter shapes and harden Ctrl-C exit * no-mistakes(review): Record OpenCode 2 live seatbelt and exit evidence * no-mistakes(test): Document OpenCode 2.0.22 wide-pane sidebar exit refusal limitation * no-mistakes(document): Refresh OpenCode 2 exit-key and seatbelt payload docs * test(opencode): verify OpenCode 1.x live with the dual-export plugins Pin the live e2e TUI panes to the resolved opencode executable, and record OpenCode 1.15.7, 1.18.20, and 1.18.31 evidence for plugin loading, the turn-end guard, watcher continuity, and 1.x worker launch and exit. * no-mistakes(review): Defer OpenCode 2 Enter on unknown composer; widen turn-end docs * no-mistakes(document): Document OpenCode 2 delivery gate unknown-composer test outcomes --------- Co-authored-by: Niranjan <nniranc@gmail.com> Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t the most recent group member (kunchenguid#7662) * fix(bin): resolve the tmux container from the caller's own session, not the most recent group member * no-mistakes(test): Fall back when the $TMUX session id is stale * no-mistakes(document): Document tmux container choice under grouped sessions
…er (kunchenguid#7647) * fix(bin): read a fully dim Cursor placeholder as an empty composer cursor-agent 2026.10.19 draws the composer glyph and its idle placeholder as one SGR 2 run with no reverse-video cell, so ghost stripping empties the row and the empty-content guard returned unknown. Relaunch and exit then refused on every idle Cursor pane on tmux. When a styled bare row strips to empty and the plain row is an agent glyph followed by an anchored idle placeholder, read it empty. Bright typed text, other dim text, and unstyled rows keep their existing verdicts. Fixes kunchenguid#7413 * docs(verification): record the fully dim Cursor placeholder verdict and refresh command
… composer as empty (kunchenguid#6163) * fix(bin): recognize a named Claude session's titled composer rule on cursorless backends `claude -n <name>` right-aligns the session name in the rule above the composer. Cursorless backends (Herdr) accepted only solid rules, so the scan saw the lower rule alone, the idle composer read `unknown`, and away-mode delivery to a named Claude primary deferred until return. A rule carrying one right-aligned title now opens a separator pair. It never closes one, the pair it opens counts only over an agent-glyph row, and it is never a valid Pi pair, so Pi's verdict is unchanged. The away-mode composer deferral now names which refusal it was: unsubmitted text, or no composer shape recognized. * no-mistakes(review): Reword composer deferral reason to cover busy harness * no-mistakes(ci): ci-3 (Greptile, user chose to fix): The rule that must always hold is that Claude's titled opening rule is accepted for any non-empty session name that doesn't start with a space and doesn't contain the rule glyph `─`, including a multiword name. The only place that check lives is the title `case` in `_fm_composer_titled_rule_row` (bin/fm-composer-lib.sh). It now reads `case "$title" in ''|' '*|*─*) return 1 ;; esac`, as the user asked, and the comment above the function mentions multiword titles. I added regression fixtures to `test_matrix_claude_named_session_titled_rule` in tests/fm-composer-lib.test.sh, next to the existing single-word `Andrew` cases. They use the title `Andrew Chen` and check that an idle composer on herdr reads `empty` and a composer with typed text reads `pending`. Something you should know: the old pattern already accepted a title with a space in the middle. It only rejected a title ending in a space. So the new multiword fixtures also pass against the old code, and the bot's failure scenario did not actually happen for "Andrew Chen". What the fix really changes is that it drops the trailing-space rejection, as instructed. Checks run: tests/fm-composer-lib.test.sh and tests/fm-composer-ghost.test.sh both pass (exit 0, no failures), and `shellcheck -x` is clean on both changed files * no-mistakes(review): Match unnamed-ghost fixture rule width to the partner rule
The fork's write_record groups a contribution record by the backlog row's project, read from the snapshot input. Upstream's new enroll subcommand (kunchenguid#7634) skipped get_input, so every enroll exited before writing and the PR health monitor never owned a branch PR. enroll now reads the input first, as retire already does. tests/fm-pr-health.test.sh resolves the contribution record through fm_test_task_dir instead of the flat data/<id>/ path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
yes, sync upstream firstmate
Measured at intake, 2026-10-23: the
upstreamremotekunchenguid/firstmatehas 22 non-merge commits this fork lacks, up to 8818a80, merge base 9fe25bc (the PR #17 sync, merged 2026-10-22); three-dot diff 108 files, 7,535 insertions, 857 deletions. Notable upstream themes: OpenCode 2 support (kunchenguid#7336); lane capacity facts with beat pressure (kunchenguid#6983); OpenRouter for typed dispatch resolution (kunchenguid#7149); notify owned PR conflicts and failing required checks behind an opt-in flag (kunchenguid#7634); read a PR with no CI checks as unverified, not green (kunchenguid#7627); Rex push notifications and grouping fixes (kunchenguid#7639, kunchenguid#7656); tmux container resolved from firstmate's own session (kunchenguid#7662); resurface queued wakes from handling successors (kunchenguid#7466); restore watcher downtime before main-only hand-back (kunchenguid#6588); away-mode inject deferral reasons (kunchenguid#6163).What Changed
kunchenguid/firstmatecommits (merge base 9fe25bc, up to 8818a80). New features: OpenCode 2 support, with version-gated launch, ported plugins under.opencode/plugins/and a sharedlib/fm-opencode2.js(feat(opencode): add OpenCode 2 support with version-gated launch, plugins, and exit kunchenguid/firstmate#7336). Lane capacity facts with beat pressure and keyed limits inbin/fm-capacity.sh(feat(bin): publish lane capacity facts with beat pressure and keyed limits kunchenguid/firstmate#6983). OpenRouter for typed dispatch resolution (feat: add OpenRouter support for typed dispatch resolution kunchenguid/firstmate#7149). Rex push notifications and cursor-aware composer detection, including the newbin/backends/rex-eventwait.lua(feat: add Rex push notifications and cursor-aware composer detection kunchenguid/firstmate#7639). An opt-in PR health monitor,bin/fm-pr-health.sh, that notifies on conflicts and failing required checks in owned PRs (feat(bin): notify owned PR conflicts and failing required checks behind an opt-in flag kunchenguid/firstmate#7634).fm-contributions.sh enrollnow reads the snapshot input before writing, asretirealready does. Without that, every enroll exited early and the PR health monitor never owned a PR.tests/fm-pr-health.test.shnow resolves contribution records throughfm_test_task_dir.Risk Assessment
enrollnow calls get_input before write_record the same way retire and verdict already do, so the merge introduces no flat data/ paths in bin. It is still a 108-file upstream sync that brings substantial new runtime behaviour (OpenCode 2, lane capacity, PR health, Rex), and that behaviour was only spot-checked here against the fork's task-data layout.Testing
I ran the real fm-contributions.sh enroll and owned commands in disposable lab homes, once with the pre-fix script and once with HEAD. Before the fix, enroll fails with exit 2 on a missing input.json and nothing is owned. At HEAD, enroll exits 0, writes data/tasks/front-client/ship1/contributions.json,
ownedlists the PR, and enrolling again is a no-op. I also booted a real Claude primary in a lab home on a private fm-lab tmux socket; its session-start digest ran to EXIT=0 with bootstrap silent (no missing or outdated tools). The Claude SessionStart hook itself stands down by design inside a gate worktree, so I drove the digest explicitly. The targeted suites all passed: PR health, contributions, contribution input, capacity facts for kunchenguid#6983, OpenRouter dispatch resolution for kunchenguid#7149, and session-start. Those suites use fixtures and fake forges, so the PR health monitor and the capacity and dispatch behaviour count as untested live. I removed the labs and the worktree is clean. There is no UI surface, so there are no screenshots.ownedreports the PR.Evidence: enroll before/after fork fix (live CLI in lab homes)
Source: enroll before/after fork fix (live CLI in lab homes)
PRE-FIX: jq: error: Could not open file .../input.json ... exit=2; owned: []; no records HEAD: exit=0; owned: [{url:https://github.com/o/r/pull/8,tasks:[ship1],final:false}]; record at ./tasks/front-client/ship1/contributions.json; re-enroll exit=0Evidence: Lab Claude primary session-start transcript
Source: Lab Claude primary session-start transcript
Evidence: Targeted suite run log
Source: Targeted suite run log
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
ownedreports the PR.Live lab home (bin/fm-lab-home.sh create), compared before and after the fix:fm-contributions.sh enroll ship1 https://github.com/o/r/pull/8, thenowned, then enroll again, run with the 87adf7b0 copy of bin/fm-contributions.sh and then with HEAD c7ed6619. The backlog row carries(repo: front-client).Live lab Claude primary started withenv -u ... TMUX_TMPDIR=$LAB/tmux tmux -L fm-lab new-session -d -s primary -e FM_HOME=$LAB claude. It was driven with send-keys to runbin/fm-session-start.sh, captured with capture-pane, and stopped withkill-serverbeforerm -rf $LAB.Traced the Claude SessionStart hook directly withbash -x bin/fm-sessionstart-run.shin the lab env.bin/fm-test-run.sh tests/fm-pr-health.test.sh tests/fm-contributions.test.sh tests/fm-fleet-snapshot-contribution-input.test.sh tests/fm-capacity.test.sh tests/fm-dispatch-resolve.test.sh tests/fm-session-start.test.sh✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.