Repository navigation
fm/upstream sync 2026 10 23 - #4
Merged
Merged
Conversation
seathief
commented
Oct 23, 2026
Owner
- feat(fm-watch): bound consecutive wedge escalations behind an opt-in cap (feat(fm-watch): bound consecutive wedge escalations behind an opt-in cap kunchenguid/firstmate#7552)
- feat(bin): archive task status history on teardown behind an opt-in flag (feat(bin): archive task status history on teardown behind an opt-in flag kunchenguid/firstmate#7407)
- feat(bin): re-raise unanswered PR review threads after an opt-in reply age (feat(bin): re-raise unanswered PR review threads after an opt-in reply age kunchenguid/firstmate#7364)
- feat(bin): refuse unguarded Herdr lifecycle briefs at spawn behind an opt-in flag (feat(bin): refuse unguarded Herdr lifecycle briefs at spawn behind an opt-in flag kunchenguid/firstmate#7631)
- fix(bin): withhold a child's PR-ready line until checks green behind an opt-in flag (fix(bin): withhold a child's PR-ready line until checks green behind an opt-in flag kunchenguid/firstmate#7630)
- feat: add auto-detected Rex session backend (feat: add auto-detected Rex session backend kunchenguid/firstmate#7633)
- fix(bin): guard the wedge cap-fire counter reset with the cap-state rollback (fix(bin): guard the wedge cap-fire counter reset with the cap-state rollback kunchenguid/firstmate#7625)
- fix(bin): restore watcher downtime before main-only hand-back (fix(bin): restore watcher downtime before main-only hand-back kunchenguid/firstmate#6588)
- test(herdr): bound the piped composer probe with fm_run_timed instead of GNU timeout (test: remove GNU timeout dependency from herdr backend test kunchenguid/firstmate#7636)
- feat: add Rex push notifications and cursor-aware composer detection (feat: add Rex push notifications and cursor-aware composer detection kunchenguid/firstmate#7639)
- feat(bin): notify owned PR conflicts and failing required checks behind an opt-in flag (feat(bin): notify owned PR conflicts and failing required checks behind an opt-in flag kunchenguid/firstmate#7634)
- test: isolate real-Rex suites on private servers (test: isolate real-Rex suites on private servers kunchenguid/firstmate#7649)
- fix: distinguish outdated tools from missing tools (fix: distinguish outdated tools from missing tools kunchenguid/firstmate#5688)
- fix(bin): clarify ahoy skill legacy session-start exclusion to require verbatim match (fix(bin): clarify ahoy skill legacy session-start exclusion to require verbatim match kunchenguid/firstmate#7530)
- fix(bin): resolve timeout/gtimeout to executable paths via type -P (fix(bin): resolve timeout/gtimeout to executable paths via type -P kunchenguid/firstmate#7637)
- fix(bin): count answered captain holds moved to the Done archive as durable (fix(bin): count answered captain holds moved to the Done archive as durable kunchenguid/firstmate#6094)
- fix(bin): read a PR with no CI checks as unverified, not checks green (fix(bin): read a PR with no CI checks as unverified, not checks green kunchenguid/firstmate#7627)
- fix(bin): correct the wedge reset-failure log line and make the fault test root-safe (fix(bin): correct the wedge reset-failure log line and make the fault test root-safe kunchenguid/firstmate#7650)
…cap (kunchenguid#7552) * feat(fm-watch): bound consecutive wedge escalations per worker window An unattended watcher escalation loop once burned ~359M tokens in a day: the watcher keeps re-escalating a pane it believes is wedged, and nothing bounded how many times it did that for one worker window. Add an opt-in ceiling on consecutive wedge escalations for a single window. At FM_WEDGE_MAX_ESCALATIONS the watcher publishes one terminal PERMANENTLY-WEDGED wake and writes both cap markers, after which the wedge path short-circuits for that window until FM_CAP_HORIZON_SECS elapses or an operator removes both markers. A pane hash change alone does not re-engage while the window-scoped marker stands, so a pane that re-renders on every poll cannot rebuild the counter and re-fire every bound. A failed marker write rolls the cap state back rather than amplifying one write failure into a wake-queue flood, and a rollback that itself fails leaves a transient sentinel that holds the wedge path quiet for FM_ROLLBACK_SENTINEL_TTL_SECS. FM_WEDGE_MAX_ESCALATIONS defaults to 0, so the unconfigured path keeps today's behavior exactly. Each setting is validated the way the rest of the file validates its environment settings and falls back to its default on a non-numeric value; a zero horizon or sentinel TTL falls back too rather than degrading into a re-fire loop. Tests drive the watcher as a real process through its public environment surface, and a separate opt-in live guard exercises a real tmux pane holding a wedged process in a disposable lab home built by bin/fm-lab-home.sh, covering both halves of the contract: the cap engaging, and the cap releasing itself on the horizon with no operator action. * no-mistakes(review): fix(fm-watch): bound the wedge cap honestly * test(fm-watch): make the wedge-cap live guard a falsifiable comparison The live guard demonstrated the ceiling but could not falsify it, and it advertised evidence that did not survive its own run. Register both new suites in bin/fm-test-run.sh. Unregistered they fell into the unclassified catch-all, which put the live guard outside the repo-wide live-gate sweep at tests/fm-live-gate.test.sh - the sweep that proves every live guard refuses together on FM_LIVE=0. The wedge-cap suite joins watcher-wake-lock and the live guard joins live-harness-optin; the serial lane passes no live-capability --fail-on-gate-skip token, so CI gating is unchanged. Run the unceiled baseline and the ceiled arm back to back on the same workload, one differing setting, in one transcript. The baseline at the shipped default of 0 shows the train of five consecutive wakes the cap exists to bound; the ceiled arm at 3 shows one terminal PERMANENTLY-WEDGED wake and then silence. State plainly in the transcript that the motivating incident happened at the default, so it motivates the ceiling and is not itself evidence the ceiling engaged. Correct the post-horizon assertion. It required the terminal marker on the very first cycle after the horizon, which the reset escalation counter makes false: the first post-horizon wake is an ordinary escalation and the bound is reached one cycle later. Assert the lane is audible, not which marker it carries. Reprint the transcript to stdout before cleanup, so the run ends in one self-contained evidence block instead of a pointer to the temp file this script is about to delete. Source tests/wake-helpers.sh for hash_text, and define PANE_TEXT in this script's own scope, so no unbound-variable or command-not-found line can appear inside the evidence a reviewer reads. Document in docs/configuration.md what the cap covers and does not: it bounds the wedge timer where a busy pane crosses FM_BUSY_TURN_MAX_SECS, it does not cover the non-terminal first-sight stale surface, only a pane leaving the stale path clears the markers, and a cap fire resets the consecutive-deferral budget of a wedge-evidence guard because that guard is not consulted while the cap is silencing the window. * no-mistakes(review): fix(fm-watch): release wedge cap markers on every episode-ending reset * no-mistakes(review): derive the live guard's window key the way the watcher does * no-mistakes(review): fix(fm-watch): probe dead endpoints under the wedge cap, scope terminal rows per episode * no-mistakes(review): fix(fm-watch): cap the dead-endpoint probe, real degraded fallback * fix(fm-watch): release the wedge cap through one episode-ending choke point The cap's release invariant - every reset that ends a wedge episode drops the cap markers with it - was wired at two inline sites and two more were missed. One of the misses is surface_nonterminal_stale, the hot path that ends the episode marker on every non-terminal stale poll, so the cap's episode tracking was broken in the common case rather than the rare one and a genuine new wedge after a pause-and-resume stayed silent for the full horizon. Enumeration had already failed once, so wiring the two missing sites would look finished and be incomplete again. Make the invariant structural instead: one choke point, wedge_episode_end, now performs every removal of the .stale-since-<key> episode marker and clears the cap markers with it, and a new assertion in the test fails on any direct rm of that marker outside it. keep-counter=1 marks a route that only restarts the escalation count while the wedge timer keeps aging. That is the same episode, not a new one, so the cap markers are deliberately not cleared there; an existing triage test pins that behaviour and it is unchanged. Both proofs are falsification-tested. The completeness assertion and the behavioural case each fail against the pre-fix watcher, and the behavioural case additionally fails when only the surface_nonterminal_stale wiring is reverted while the choke point still exists, so it pins the hot path specifically rather than passing because some other route happened to release the markers. The cap stays window-scoped with no per-hash marker, still defaults to off, and still releases on the horizon. The episode-scoped terminal row stays one per episode under the append lock, the documented first-sight containment stays, and the dead-endpoint-before-silencing work is untouched. * no-mistakes(review): fix(wm-watch): release wedge cap on every episode-end route * no-mistakes(review): fix(fm-watch): gate cap markers, probe under sentinel, release churn * no-mistakes(review): fix(fm-watch): release wedge cap on the away first sight * no-mistakes(review): fail safe the wedge escalation-count write under a configured cap * no-mistakes(test): fix wedge-cap live guard leaking its temp root * no-mistakes(review): fix(fm-watch): keep the wedge ladder reachable under a fired cap * no-mistakes(review): fix(fm-watch): pace the sentinel dead-record probe by threshold * no-mistakes(test): fix(test): give the wedge-cap suite its serial-lane weight hint so its CI shard fits the 30-minute job cap * no-mistakes(document): align wedge-cap docs with implemented release boundary * fix(docs): correct the cap markers' lifetimes in the layout reference The state/ listing claimed .wedge-permanent-<key> "only sits on disk until a later cap fire overwrites it". That is false: bin/fm-watch.sh:1214-1219 _wedge_cap_markers_clear removes .wedge-permanent-<key>, .wedge-terminal-queued-<key> and .wedge-rollback-failed-<key> outright, and it runs on every cap release, so a release takes the marker off disk rather than leaving it for a later cap fire to overwrite. The same clause also left the other two markers' lifetimes and delete semantics unstated. Each now carries its own lifetime, and the sentence records that a cap release drops all three while only the two silencing markers re-engage a window when deleted by hand. Verified against bin/fm-watch.sh: FM_CAP_HORIZON_SECS bounds the cap marker via _wedge_cap_marker_stands, FM_ROLLBACK_SENTINEL_TTL_SECS bounds the rollback-failure sentinel, and the terminal-row marker records the episode that queued the row and is dropped with the rest of the cap state. * no-mistakes(document): state the watcher's cap release boundary in its header comment * fix(fm-watch): make the wedge escalation-counter reset opt-in The upstream maintainer reviewed the wedge-cap pull request, accepted the live validation, and asked for the escalation-counter reset to become opt-in: it should only kick in when the cap is configured, so a home that has not set anything keeps today's behaviour and still gets the deep-inspection prompt for a stuck pane. The choke point wedge_episode_end gained a third state. The call sites whose pre-cap shape kept the escalation counter - the first-sight stale surfaces, where upstream dropped the wedge timer and left the count standing - now pass keep-count, so the counter survives them exactly as upstream's did while the cap is unconfigured. With a configured cap the count still restarts with the released episode, which is what lets a re-wedged window escalate from 1 again past a released marker. The other episode-ending routes are unchanged: the pre-cap watcher reset the count there too, so those keep resetting it in both postures. The new parity suite proves the property rather than asserting it: it drives one stuck-pane scenario against upstream main's own bin/ tree and against this branch, and requires the same observable outcome for the unconfigured case - the same ladder, the same demand-deep-inspection prompt, no cap marker. A configured cap in the same harness fires PERMANENTLY-WEDGED at its threshold, which is what shows the setting really reaches the watcher, so the unconfigured cases cannot pass vacuously. * no-mistakes(review): fix(fm-watch): bound the wedge cap settings to a comparable range The review step's fix agent produced this change but its own turn hit the 30-minute invocation budget, so the gate failed the step with the work uncommitted. The work is preserved in the gate's recovery ref and in the task's saved round patch; this commit carries it forward unchanged. bash's test cannot compare a decimal integer outside its own range: the comparison ERRORS rather than evaluating false, and every later comparison of such a value therefore reads as false. A value such as 99999999999999999999 passed the digit check unchanged, so the cap never engaged - the unbounded escalation train the feature exists to bound - and the \"is it zero\" test errored false as well, so the keep-count sites reset the escalation counter on a home that had configured nothing, which is the behaviour the maintainer's request forbids. The same hole let an unusable horizon make the cap marker expire at once and an unusable sentinel TTL drop the sentinel on the next poll, putting the failed-write route back on every poll. The fix bounds the accepted range at the ONE normalization choke point, where each value is read, rather than hardening every comparison site: a value bash cannot compare never reaches a comparison, because an arithmetic error is read as false at every site, which is the opposite of what each of those tests means. Values are accepted below 10^18, which always fits bash's 64-bit signed arithmetic, and the empty, non-digit, zero and negative handling keeps its documented fallbacks: 0 for the cap, 86400 for the horizon, 3600 for the sentinel. Regression cases cover a value beyond bash's integer range for each of the three settings, and the parity suite proves the unconfigured home is still observably identical to upstream main. * no-mistakes(document): docs: correct wedge-cap ranges, release routes, reset contract * no-mistakes(ci): fix the wedge-cap lint finding and pack the lane on CI measurements Upstream CI on the rework head went red on two checks, both ours. "Lint 1" failed on the new parity suite: two `return 1` statements behind `fail` were unreachable, and behind them was a real bug. `fail` lives in tests/lib.sh and exits, so inside the command substitution that reads the baseline it ended only the SUBSHELL - the outer script continued with an EMPTY baseline and reported a divergence with an empty trace instead of failing. The test's own header says it is the proof and a silent skip would be no proof at all, so the baseline now reports the specific reason and returns non-zero, and the caller's guard aborts the whole test. A baseline that cannot be materialised aborts the test rather than continuing. "Behavior portable parallel 2" failed the packing-boundary check: the wedge-cap suites were packed on provisional LOCAL measurements that overstated their CI cost, which left the serial lane without the headroom the boundary check needs to place one script exactly at the budget. The same CI run measured them for real - 374225 ms for the wedge-cap suite, 27763 ms for the parity suite, 74 ms for the live-guard skip - and those Ubuntu measurements replace the local ones in bin/fm-test-run.sh. * no-mistakes(document): docs: fix wedge-cap measurement attribution, document cap-off release
…lag (kunchenguid#7407) * feat: archive retired task status history behind an opt-in flag * fix(bin): recognize an already-retired remote home before it is recreated Sourcing fm-wake-lib.sh through the control script's libraries creates "$FM_HOME/state", so retiring a remote home that was already removed fabricated an unseeded directory before validate_home ran. The already-retired branch was unreachable, and that retirement and every retry refused with "remote home is not a seeded secondmate home". Check for the absent home before loading any library, accepting exactly the retire dispatcher's arguments so an invalid option still fails with usage, and drop the allow-absent branch that could no longer be reached. * no-mistakes(review): Removed configurable status archive retention override * no-mistakes(review): Remove published archives after failed status retirement
…y age (kunchenguid#7364) * fix(bin): raise unanswered review threads again after acknowledgement Acknowledging a maintainer signal removed the only record of the thread, so a comment nobody answered was never raised again, including while its task sat on a hold. Poll now tracks each unanswered maintainer comment, review and inline review comment on an open pull request until the contribution author replies in it or it is resolved, and raises it again once it is a day old (FM_CONTRIBUTIONS_REPLY_AGE) and daily after that. The wake names the comment URL and its age. * no-mistakes(review): Track only inline and changes-requested threads, bound notified * no-mistakes(review): Let reviewer verdicts answer reviews, skip empty-body change requests * no-mistakes(ci): Fixed "Behavior portable serial 6" with a test-only change. The serial 4 failure is a timing flake that this change did not cause. Serial 6 (tests/fm-pr-check-security.test.sh, "retired poll left task-a.check.sh"): this PR adds one more forge read to the contribution observer, a paginated `gh api graphql` query for reviewThreads. The test's fake `gh` answered every `api graphql` call with merge-state key=value lines. The observer rejected that output and raised the wake "contributions: observation unavailable ... review thread pages returned an unexpected shape". That wake ended the watcher cycle before the merged task-a poll retired. Invariant: the fake forge must answer every read the contribution observer makes, in that read's real shape. The other contribution reads (comments, reviews, inline comments, checks, statuses, repo) were already faked. The new reviewThreads query was the only read missing. Fix: tests/fm-pr-check-security.test.sh now answers any `api graphql` call containing reviewThreads with one empty reviewThreads page. The merge-state graphql answer is unchanged. Verified: I reproduced the failure locally before the fix. The test also passes on base fc8972c with that commit's bin/. After the fix, `bash tests/fm-pr-check-security.test.sh` exits 0 with all 47 cases ok. The only other test whose fake `gh` handles `api graphql` and runs the contribution path is tests/fm-pr-merge.test.sh. It prints no "unexpected shape" text. It fails locally with "github-zero-exit-queue-required: refusal did not name the concrete observed state", and it fails the same way on base fc8972c. So that failure predates this change, and it is not among the failing CI checks. Serial 4 (tests/fm-remote-reply.test.sh, "the NUL-bearing line was not captured"): this is a 40-second wait (800 polls of 0.05 seconds) for a procevent result in the remote-reply stream, and it timed out on CI. The test does not use fm-contributions or any file this PR changes. It passed locally twice (42 ok, exit 0), so this is CI load flakiness outside this change. I made no code change for it * fix(bin): make unanswered review re-raise opt-in The re-raise wake and the reviewThreads read now run only when FM_CONTRIBUTIONS_REPLY_AGE is set to a positive number of seconds. An unconfigured install polls exactly as before: no review-thread read, no threads[] entry and no unanswered signal. arm copies a set value into the generated check so the watcher honors it. * fix(bin): refuse a zero-padded zero reply age FM_CONTRIBUTIONS_REPLY_AGE=00 and 000 passed validation as positive ages. They now fail like 0 does. * no-mistakes(review): remove dead reviewThreads branch from security test fake gh * no-mistakes(review): use portable sed -i.bak in contributions test helper * no-mistakes(docs): name the thread URL in the unanswered-wake header text
… opt-in flag (kunchenguid#7631) * Refuse unguarded Herdr lifecycle briefs at spawn * no-mistakes(review): name matched Herdr command, document opt-in guard, test scaffold * no-mistakes(lint): quote matched-command assertion so ShellCheck SC2016 passes
…an opt-in flag (kunchenguid#7630) * fix(bin): publish a child's PR-ready line only after checks green behind an opt-in flag bin/fm-pr-check.sh publishes the child's PR-ready line to the parent channel when it registers the PR, before the child has reported its checks green. With config/ready-after-green present, a no-mistakes registration records the PR and arms the merge poll but publishes the line only when the last done line in the child's status is 'done: PR <url> checks green' for that PR. Without the flag the behaviour is unchanged. * no-mistakes(review): accept checks-green note with surrounding text for ready line * no-mistakes(ci): Lint 2 was caused by this PR and is fixed. Behavior tests (Herdr) is a pre-existing flake unrelated to this PR, so no code change was made for it. Lint 2 (fixed): ShellCheck SC2034 at tests/fm-teardown.test.sh:2711 because `run` was declared with `local` but is a function defined inside the test, never a variable. Invariant: a `local` declaration names only variables the function uses. The same wrong declaration also existed in the sibling opt-in test at line 2662 (both added by this PR), so `run` was removed from both `local` lines. Verified locally: `shellcheck -x tests/fm-teardown.test.sh` returns 0 (it reproduced the SC2034 warning before the edit), `bin/fm-lint.sh tests/fm-teardown.test.sh` returns 0, and `bash tests/fm-teardown.test.sh` exits 0 with 141 passing tests, including both "fm-pr-check withholds the PR-ready line ..." and "fm-pr-check releases the PR-ready line on a checks-green report that carries surrounding text". Behavior tests (Herdr) (not caused by this PR): the single failure is tests/fm-backend-herdr-presentation-e2e.test.sh "concurrent secondmate recovery failed ... error: herdr presentation recovery could not acquire its session lock; refusing a concurrent resume". That test exercises bin/fm-spawn.sh's Herdr session-lock recovery path; this PR touches only bin/fm-pr-check.sh, docs, a SKILL.md row, and tests/fm-teardown.test.sh, and the presentation test references none of them. The identical failure appeared on the unrelated branch fm/t3-5596-successor-v3 in run 39709923982 (job 119598556006), and the flake is already tracked in open issues kunchenguid#6454 and kunchenguid#7277. Rerunning the Herdr job should be enough; fixing the lock budget belongs to those issues, not this PR
* feat(bin): add Rex as an auto-detected session backend Add bin/backends/rex.sh, a session-provider adapter for the Rex terminal multiplexer, and wire it through the dispatcher, spawn, control plane, crew state, busy evidence, and away-mode supervision. - Auto-detect Rex from TERM_PROGRAM=rex with REX_BLOCK, after tmux and Herdr and before cmux; explicit --backend, FM_BACKEND, and config/backend still win, and a nested tmux or Herdr is never read as Rex. - One focus-safe tab per task: in Firstmate's own Rex session when it runs directly inside Rex (confirmed against the server), otherwise in one session per home; new blocks are resized to 200x50; every call names its session and block by immutable id. - Recovery-grade agent state from Rex's process view, OSC 7501 program status as positive busy evidence only while its owner is in the foreground chain, and a viewport derived from the last size.rows rows of the styled capture. - Absence proof from a structured not-found on the recorded server (rex_server), so fm-control exit and relaunch work on Rex and a gone block is reclaimed into a fresh tab in its recorded session. - Away mode for a Rex-hosted primary runs its daemon in a dedicated Rex session killed by exact id. - Docs: docs/rex-backend.md, configuration and control-plane tables, and a dated live verification record; tests: a stateful fake-CLI suite, a real-server smoke test guarded to its own fm-test- session, and a Rex away-launcher e2e. * no-mistakes(review): Recover Rex reclaim after server stops; document deferred asks * no-mistakes(document): Correct Rex documentation and consolidate stale backend guidance
…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
…26-10-23 # Conflicts: # .agents/skills/operational-home-layout/SKILL.md
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.