feat(bin): derive and self-refresh the two mechanical sections of the context briefs - #1876
Open
JaredHuynhning wants to merge 8 commits into
Open
feat(bin): derive and self-refresh the two mechanical sections of the context briefs#1876JaredHuynhning wants to merge 8 commits into
JaredHuynhning wants to merge 8 commits into
Conversation
…cal sections The captain reads three per-context briefs to reload himself on a project. Two of their four sections were hand-maintained and drifted, and a brief that is quietly two days behind looks exactly like one that is current. bin/fm-context-briefs.sh now owns those two sections. "Waiting on you" comes from captain-held backlog items read through tasks-axi, grouped by repository and clustered under the work that raised each set of decisions, with any item carrying a recorded date lifted to the top. "Running now" comes from state/<id>.meta reconciled by bin/fm-crew-state.sh, and says so plainly when nothing is running. The narrative and the pointers are never touched. The boundary is four explicit markers. A brief whose markers are missing, duplicated, reversed, or overlapping is refused and left untouched, so the generator never guesses a boundary and never appends. Hand-written prose immediately either side of a block survives byte for byte, and the test that proves it is itself proved against a naive heading-anchored rewrite first. Both halves show their own age. Each generated block opens with the date and time it was generated, worded so an old date reads as a warning rather than as freshness, and --check audits those stamps plus the narrative's own review line without writing anything. Regeneration is wired to the lifecycle moments the records change rather than to a schedule: opening or resolving a captain decision, spawning a task, landing work through either merge path, and finishing a task. Each calls --after-event, which is silent, no-ops without a mapping, and always exits 0 so a lifecycle command never fails because a reading surface could not refresh. tests/fm-context-briefs.test.sh demonstrates that by driving a real local landing end to end rather than asserting the wiring. config/context-briefs.conf maps contexts to repositories, hand-edited and gitignored because the grouping is a human judgement. A repository present in the records that no context claims is reported as an explicit line.
5 tasks
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
The captain reads three per-context briefs to reload himself on a project without asking firstmate anything. Two of their four sections can be derived from records that already exist, so derive them, and make that regeneration happen on its own rather than when somebody remembers. His words on 07/08/2026, choosing this over hand-maintenance: the mechanical sections must be "always current" and "cannot drift".
Why this exists and the failure it is built against: the captain loses the thread when several projects arrive at once, and the briefs give him somewhere to look instead. A brief that is quietly two days behind looks exactly like one that is current, which makes a stale brief worse than no brief. The same day produced three separate instances of that class: a service running code three days old while every signal read green, a page reporting a date taken from the clock, and a login gate nobody could see. Anything hand-maintained drifts and anything derived from a record does not.
The three files already exist at data/briefs/back-office.md, data/briefs/growth.md and data/briefs/the-machine.md, surfaced to the captain through a symlink at ~/Knowledge/Firstmate so they render in Obsidian. Their existing shape is the contract. This task automates two of their four sections and does not redesign them.
The context to repository mapping is the captain's own grouping and is not derivable: back-office covers mldinvoicing, mldaccounts and mld-bi; growth covers mld-website and MLDExpansion; the-machine covers firstmate and transformation. That mapping had to live somewhere a human can edit without touching code, and an unmapped repository had to become a visible line in the output rather than being silently dropped.
What was asked for:
Regenerate the two derivable sections and only those two. "Waiting on you" comes from the captain-held backlog items for that context's repositories, grouped so the list is readable rather than a dump of thirty ids, with any item carrying a genuine deadline preserved at the top, read through the compatible backlog tooling rather than by parsing the markdown by hand. "Running now" comes from live task state, state/*.meta plus the current-state reader, and says so plainly when nothing is running. The narrative and the pointers are hand-written and the generator must never touch a character of them.
Make the boundary impossible to get wrong. Explicit begin and end markers around each generated block. A run that cannot find its markers must refuse and say so, never guess and never append. A test had to put hand-written prose immediately either side of a block, regenerate, and prove the prose byte-identical afterwards, and that test had to be proved red before it was proved green, meaning it must fail against a naive implementation.
Stamp both halves with their own age. Every generated block records when it was generated. The narrative section carries a last-reviewed date that the generator reads but never writes. A brief must show its own age rather than imply freshness, so the captain can see at a glance which half he is trusting.
Run it without being asked, at the moments the captain named: work lands, a decision resolves, a job finishes, or he asks. Wire it to the existing lifecycle points rather than to a schedule, because a timer is the wrong mechanism and this repository's own rule says a scheduled check needs a continuous probe beside it. Also make it runnable by hand for the fourth case.
Make a broken generator visible. If it stops running the blocks silently freeze and the captain keeps trusting them, which is exactly the failure this task exists to prevent. The generated block's own timestamp is that probe, so an old one must be visible in the rendered file rather than only in a log.
Definition of done as observable conditions: running the generator updates only the two derived sections in all three files with no other line changed; hand-written prose adjacent to a block survives regeneration byte-identically, proven by a test that fails against a naive implementation; a file with a missing or damaged marker causes a refusal with a clear message and is left untouched; every generated block states when it was generated and the narrative states when it was last reviewed; the generator fires on its own at the lifecycle points above, demonstrated rather than asserted; an unmapped repository appears in the output as an explicit line; and the three existing briefs still read as documents a person would want to read, checked by opening them rather than only diffing them.
Constraints that were given: follow the firstmate-coding-guidelines skill because this changes firstmate's shared tracked material; data/ is private and gitignored and the three briefs must never be committed; do not invent content, and if a section has no data say so plainly rather than writing filler; no em-dashes and no semicolons in anything a person reads, which includes every line this generator emits; Australian English.
Deliberate decisions taken while doing the work, which a reviewer reading only the diff would not know were chosen on purpose:
Task spawn was added as a fifth lifecycle refresh point beyond the four the captain named. Without it, "Running now" is wrong from the moment a task starts until some unrelated event fires, which is the drift the task exists to prevent. It costs roughly six seconds on a spawn, which was accepted and reported to the captain.
Persistent secondmates are deliberately excluded from "Running now". A secondmate is a standing direct report rather than a work item and an idle one is healthy, per AGENTS.md section 10, so listing one as running work would be wrong.
An unmapped repository is reported as an explicit stdout line and by --check, and is deliberately not filed into any brief. Putting it under an arbitrary context would be worse than reporting it, and putting it in all three would be noise. This is reported as a fact and never as a failure exit status.
--install-markers is a separate, explicit, one-time subcommand rather than something the generate path does on its own. That is what lets the generate path refuse strictly instead of ever inserting a boundary it inferred. It deletes nothing.
--check exists as a read-only audit but was deliberately NOT wired into the session-start bootstrap diagnostics. The captain's stated probe is the timestamp in the rendered file, and adding a bootstrap diagnostic token would have meant new machinery across AGENTS.md, the bootstrap header and the bootstrap-diagnostics skill for a signal the file already carries.
Intermediate records write "-" for an absent field rather than an empty one, because a tab is IFS whitespace and bash
readcollapses two adjacent tabs into one, which silently shifts every later column. This was a real bug found and fixed during the work, and the sentinel is the fix rather than a stylistic choice.Source decision titles are relayed verbatim even when a captain-authored title contains a semicolon. Rewriting a record the captain wrote would be inventing content, which the task forbids. The no-semicolon constraint is honoured in every line the generator itself writes.
The generator wraps tasks-axi in a
cd "$FM_HOME"helper, matching how bin/fm-decision-hold.sh already resolves the backlog, so the reads resolve against this home rather than the caller's working directory.Several shellcheck SC2016 disables are present because the format strings contain Markdown code-span backticks, which a shell linter cannot tell from command substitution.
The new test is mapped to the snapshot-bearings family in bin/fm-test-run.sh, and lands in the portable-serial lane by default.
Verification already performed: bin/fm-lint.sh exits 0 across the repository, bin/fm-doc-audience-check.sh exits 0 with the new operator-example registered, bin/fm-test-run.sh --check-coverage passes, and the new suite plus the affected families (pr-forge, backend-dispatch, pure-contract-unit, snapshot-bearings, plus fm-test-run, fm-documentation-audiences, fm-lint and fm-gitignore-config) run green. The generator was also exercised against a throwaway copy of the captain's real briefs and backlog, and all three rendered files were read rather than only diffed.
One pre-existing failure is present and is NOT caused by this change: tests/fm-teardown.test.sh fails on "herdr-preflight-missing-adapter: the retryable pre-return refusal was not explained visibly". It was confirmed to fail identically on the unmodified base commit with this branch's changes stashed.
What Changed
bin/fm-context-briefs.sh, which regenerates the "Waiting on you" and "Running now" sections of the three per-context briefs underdata/briefs/from the captain-held backlog (read through the tasks-axi tooling) and livestate/*.metaplus the current-state reader, grouping backlog items for readability, floating genuine deadlines to the top, excluding persistent secondmates, and stamping each block with its own generation time while reading but never writing the narrative's last-reviewed date. Rewrites are bounded by explicit begin/end markers: a missing or damaged marker refuses with a message and leaves the file byte-identical, staged writes are locked viafm_lock_acquire_waitwith an EXIT trap cleaning temps, and unmapped repositories are reported as an explicit stdout line rather than filed anywhere.--after-eventrefresh into the five lifecycle points (fm-spawn.sh,fm-teardown.sh,fm-pr-merge.sh,fm-merge-local.sh,fm-decision-hold.sh) under a single aggregate timeout, and added the manual--checkread-only audit (a pure file read that performs no current-state work) and the one-time--install-markerssubcommand, which inserts boundaries per heading and deletes nothing.FM_CONTEXT_BRIEFS_*knobs indocs/configuration.mdwith an operator example atdocs/examples/context-briefs.conf, listed the script indocs/scripts.mdand the audience registry, and addedtests/fm-context-briefs.test.sh(35 cases) mapped to the snapshot-bearings family inbin/fm-test-run.sh, including a red-first proof that the prose-survival check fails a naive heading-anchored rewrite.Risk Assessment
✅ Low: The authorised fix is small, correct and verified end to end against the real tasks-axi with both surfaces now agreeing byte for byte, every load-bearing guarantee from the earlier rounds still holds under manual exercise, and the only remaining item is a bounded duplicate line with a one-line remedy.
Testing
Ran the new 35-case context-briefs suite green, then demonstrated the intent end-to-end against a fixture home mirroring the captain's three real contexts: regeneration rewrote only the two marker-bounded sections with adjacent hand-written prose byte-identical, a damaged marker produced a clear refusal leaving the file untouched, every generated block carried its own age while the narrative review line was only read, a backdated block surfaced as stale in both the rendered file and --check, an unmapped repository appeared as an explicit non-failing line, and a real fm-merge-local.sh landing refreshed the briefs with no generator call. Captured full-page screenshots of all three rendered briefs and read them as documents. Also ran the two other affected lifecycle suites green and confirmed by ablation that the flagged fm-teardown failure is pre-existing and unrelated. Worktree left clean.
/var/folders/_s/01tmj3vx0bn03t1t29k76npw0000gn/T/no-mistakes-evidence/01KZDF9HD8QF7RQV49EDV81GG1/back-office-rendered.png)/var/folders/_s/01tmj3vx0bn03t1t29k76npw0000gn/T/no-mistakes-evidence/01KZDF9HD8QF7RQV49EDV81GG1/growth-rendered.png)/var/folders/_s/01tmj3vx0bn03t1t29k76npw0000gn/T/no-mistakes-evidence/01KZDF9HD8QF7RQV49EDV81GG1/the-machine-rendered.png)Evidence: Generator stdout: unmapped repository reported as an explicit line, three briefs updated
Evidence: Only the two derived sections changed: diff of everything outside the markers is empty
Evidence: Damaged marker refusal, file left byte-identical, and the read-only --check audit
Evidence: Staleness probe: a three-day-old block is visible in the rendered file and fails --check
Evidence: Lifecycle auto-refresh: a real fm-merge-local.sh landing refreshed the brief with no generator call
Evidence: Generated back-office brief in full (markdown source)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-context-briefs.sh:656- install_one duplicates the boundary line into the generated block when the heading is immediately followed by the next##heading or---rule. With stop == h+1 the rangesed -n "$((h+1)),$((stop-1))p"is reversed, and POSIX sed then prints the first address's line, whichtail -n "+$stop"prints again. Reproduced against a throwaway home: a brief containing## Waiting on youdirectly followed by## Running nowcame back with## Running nowcopied inside the waiting-on-you block. That also breaks the stated definition of done ("a file with a missing or damaged marker causes a refusal with a clear message and is left untouched"), because install_one for the second heading then refuses with "so it was left untouched" about a file the first install_one had already rewritten. Guard the empty range (skip the sed when stop == h+1) before --install-markers is pointed at any new brief.bin/fm-context-briefs.sh:632- install_markers guards withWOU_BEGIN present AND RN_BEGIN present, so a half-marked brief re-runs install_one for the pair that is already there and inserts a second copy of it. Reproduced: a second --install-markers run on a partially installed file produced twowaiting-on-you:beginlines, after which check_markers refuses that brief forever and --install-markers cannot repair it because it deletes nothing. Guard each install_one by its own begin marker, and stage both insertions into one temp file so a refusal on the second heading leaves the original byte-identical, as the refusal message already claims.bin/fm-context-briefs.sh:259- captain_items gates only oncommand -v tasks-axi, bypassing the repo's shared owner of that decision,fm_tasks_axi_backend_availablein bin/fm-tasks-axi-lib.sh, which fm-session-start.sh:456, fm-teardown.sh:833 and fm-decision-hold.sh:104 all use. When tasks-axi is absent, below the 0.2.4 floor, a stripped build, orconfig/backlog-backend=manual, the read returns nothing and render_waiting emits "Nothing is waiting on you for this side." with a fresh timestamp. The intent requires the backlog be "read through the compatible backlog tooling" and that a section with no data "say so plainly rather than writing filler"; a confident false "nothing is waiting" is the drift this feature exists to prevent, and the timestamp probe cannot catch it because the block genuinely is fresh. Distinguish "the backlog could not be read" from "no items".bin/fm-context-briefs.sh:536- check_markers resolves four line numbers and replace_block then head/tails on them, with no lock held in between, while five entrypoints (fm-spawn.sh, fm-teardown.sh, fm-pr-merge.sh, fm-merge-local.sh, fm-decision-hold.sh) can fire concurrently in a fleet. If a second run'smvlands between the first run's check_markers and its replace_block, head -n and tail -n +N cut at stale offsets and destroy hand-written prose, the one guarantee the change is built around, in a file under gitignored data/ with no version-control recovery. The repo already owns this boundary: wrap generate (or the per-file rewrite) in fm_lock_acquire_wait from bin/fm-wake-lib.sh, as fm-backlog-handoff.sh and fm-bootstrap.sh do.bin/fm-context-briefs.sh:668- --after-event now runs synchronously at the end of every spawn, teardown, PR merge, local merge and decision hold/resolve, but nothing bounds the refresh as a whole. captain_items issues one unboundedtasks-axi show --fullper captain-held item (measured ~47ms each locally, so a 60-item backlog is ~3s of process launches), and running_items issues one fm-crew-state.sh per live task bounded at 20s individually with no aggregate cap, so a degraded backend with eight live tasks can add minutes to a spawn. bin/fm-session-start.sh already treats exactly these subprocesses as unbounded and runs them under one aggregate FM_SESSION_START_TIMEOUT; wrap the --after-event generate in fm_run_timed with a single aggregate bound so a hung tool cannot stall a lifecycle command.bin/fm-context-briefs.sh:574- The intent records as a deliberate decision that "an unmapped repository is reported as an explicit stdout line and by --check", but check() never calls report_unmapped: it only walks contexts for markers, stamps and the review line. report_unmapped is invoked solely from generate() at line 527, so the read-only audit cannot surface an unmapped repository. Either call report_unmapped from check() or correct the claim.bin/fm-context-briefs.sh:76- FM_CONTEXT_BRIEFS_MAX_AGE_HOURS is used directly in[ "$age" -ge "$MAX_AGE_HOURS" ]with no validation, while FM_CONTEXT_BRIEFS_TIMEOUT two lines below is validated. A non-numeric value makes the test error and return 2, which reads as "not stale", so a typo silently disables the staleness bound that is the feature's stated probe. Apply the samecase ... *[!0-9]*reset used for STATE_TIMEOUT.bin/fm-context-briefs.sh:531-[ ! -f "$file" ] || [ -L "$file" ]collapses two different conditions into one message, so a brief that exists but is a symlink is refused with "there is no brief at $file", which is false. Given the briefs are surfaced to the captain through a symlink, an operator who symlinks individual files rather than the directory gets a refusal that points at the wrong cause. Split the message so the symlink case says it refuses to write through a symlink.🔧 Fix: gate brief sources, bound refreshes, fix marker install
4 issues (2 warnings, 2 infos) still open:
bin/fm-context-briefs.sh:105- Sourcing bin/fm-wake-lib.sh at file scope pulls in its unconditional top-levelmkdir -p "$STATE"(fm-wake-lib.sh:16), which runs on every invocation before the case statement, including the two paths that promise not to do this. Reproduced:--helpagainst a home with no state/ created it, so--checkwrites despite the header at line 27 and docs/configuration.md both saying it "writes nothing"; and--after-eventagainst a home whose $STATE parent is not writable exited 0 but printedmkdir: .../state: Permission deniedto the lifecycle command's stderr, breaking the silence contract that tests/fm-context-briefs.test.sh:427 asserts (its fixture always has a state dir, so it cannot catch this). A side consequence is that running_readable()'s absent-directory case is now unreachable, leaving the new guard covering only the unreadable-permissions case its test stages. bin/fm-crew-state.sh, the repo's other read-only reader, deliberately does not source this library. Source it lazily inside acquire_briefs_lock, the only consumer, so the lock owner is still the shared one but its mkdir stays off the read-only and silent paths.bin/fm-context-briefs.sh:756- check() now calls collect_records, which runs running_items, which launches one 20s-bounded fm-crew-state.sh plus onetasks show --fullper live task. report_unmapped only ever reads column 2 of running.tsv, the repo_key, and that comes frommeta_field "$meta" projectwith no subprocess at all. So a read-only audit that previously did pure file reads can now block for minutes on a degraded fleet (ten live tasks at the 20s per-task bound is up to 200s) for data it discards, and unlike --after-event there is no aggregate bound on this path. Collect only the repo identities check actually needs, or reuse the record collection without the current-state read.bin/fm-context-briefs.sh:75- The header states "A refresh that hits the bound leaves every brief exactly as it found it", repeated in docs/configuration.md:176. Staging is per brief inside the generate loop, so a bound hit after the second of three briefs has been moved into place leaves two refreshed and one stale. The per-brief guarantee is real and is exactly what was asked for, but the wording generalises it to the whole run, and this repository's standard is that a stated guarantee matches the behaviour. Reword both places to say the brief being written is left untouched.bin/fm-context-briefs.sh:609- When fm_run_timed kills the bounded --after-event child's process group, the mktemp workdir under TMPDIR and any in-flight.fm-context-briefs.XXXXXXstaged beside the brief are left behind. A killed run used to be accidental; the aggregate bound makes it a designed outcome, and data/briefs is the directory the captain browses through the Obsidian symlink. The lock itself self-heals because fm_lock_try_acquire reclaims a dead holder, so an EXIT trap removing the staging file and workdir is all that is missing.🔧 Fix: keep read-only paths silent, clean staged temps
3 issues (1 warning, 2 infos) still open:
bin/fm-context-briefs.sh:427- Splitting running_items changed the loop fromfor meta in "$STATE"/*.metatowhile IFS="$TAB" read -r id rk ... done < <(running_identities), so the loop body's stdin is now the identity stream itself. Every child in that body inherits it: fm-crew-state.sh at line 421 and itsno-mistakes axi status,no-mistakes runsandtmuxdescendants (nothing in fm-crew-state.sh, fm_nm_run or fm_nm_run_bounded redirects stdin), plustasks show --fullthrough task_title. A child that reads one line steals a live task from the list. Demonstrated with identical fixtures and stdin from /dev/null: this commit rendered 2 of 4 live tasks where fe8eacd rendered 4 of 4, and the truncated block still carried a fresh generation stamp, which is the silently-wrong-but-fresh failure this feature exists to prevent and which the staleness probe cannot catch. I could not make the real reader consume stdin in this sandbox because it short-circuits before the no-mistakes call when the backend target is gone, so the mechanism is proven and the live trigger is not. The repository's other per-task fan-out, bin/fm-fleet-snapshot.sh:410, keeps the plainfor meta in "$STATE"/*.metashape for the same class of reason. Redirect the state and title reads from /dev/null, or feed the loop on fd 3 withread -u 3, or keep the for-loop shape.bin/fm-context-briefs.sh:80- The header now states "--checkwrites nothing at all, so it neither creates a directory nor stages a file", repeated in docs/configuration.md as "writes nothing at all including creating no directory". Line 819 creates${TMPDIR:-/tmp}/fm-context-briefs-check.XXXXXX. The home is genuinely untouched, which I verified, and that is the part that matters, but the claim is absolute and this is the same class of overstatement that was corrected one round ago. Either scope the wording to the home, or build the audit's two record streams without a temp directory since it only needs them long enough for report_unmapped.bin/fm-context-briefs.sh:690- The expensive half of the audit is gone and the new control-backed test proves it: --check no longer performs a current-state read, so the minutes-long risk on a degraded fleet is closed. collect_records still calls captain_items on the identities path though, so the audit launches the three tasks-axi compatibility probes plus one unboundedtasks show --fullper captain-held item (measured ~47ms each, so roughly three seconds on a sixty-item backlog) and keeps only therepofield from each. If --check is meant to be the pure file read it was asked to become, take the repository from therepocolumntasks listalready prints, or gate the per-item show behind the state depth the same way the running read now is.🔧 Fix: stop children stealing records, make audit a pure read
1 warning still open:
bin/fm-context-briefs.sh:396- A captain-held item with no repository makes both surfaces name a repository that does not exist, and this commit made them disagree on what to call it. Reproduced against the real tasks-axi 0.2.4, which lists an absent repo as the quoted literal"-": captain_identities takes field[at["repo"]] verbatim andsub(/^.*\//, "", key)leaves the quotes, so --check printsunmapped: repository ""-"", while generate keeps printingunmapped: repository "-"because captain_items strips the quotes to empty and report_unmapped'swhile IFS="$TAB" read -r key displayskips the leading tab, promoting the display sentinel into the key. Checked across the branch: the generate-side"-"is present in all four commits and is not introduced here, the""-""divergence is. Neither is invented content in a brief, only on stdout, but the feature's premise is that the platform never asserts something it did not verify, and --check is one of the two surfaces the definition of done names. Fix at the shared boundary both producers feed: have report_unmapped treat an empty or-key as absent rather than as a repository, and strip tasks-axi's quoting in captain_identities so the audit and the generate path agree on the value.🔧 Fix: report a record with no repository as such
1 info still open:
bin/fm-context-briefs.sh:875- The thirdkindcolumn is appended beforeseen=$(LC_ALL=C sort -u), so two rows that were previously identical no longer collapse: a repository that appears both as a captain decision's repo and as a live task's project is now reported twice. Reproduced with one fixture across the branch, identically on both surfaces:shared-orphanwas reported once at ff12dda and e3b09bd and twice at 295fa17. Nothing stated is wrong and it is bounded at two lines per repository, but the definition of done words it as an explicit line and this is the normal shape for a new project not yet added to the mapping, so the noise lands exactly when the report matters. The kind is only needed to word the sentinel rows, so classify first and dedupe the named-repository rows on key and display alone, or drop duplicates from the emitted messages.✅ **Test** - passed
✅ No issues found.
bash tests/fm-context-briefs.test.sh(35 cases, exit 0, including the red-first proof that the prose-survival check fails a naive heading-anchored rewrite)Built a fixture FM_HOME with the captain's real mapping (back-office/growth/the-machine over mldinvoicing, mldaccounts, mld-bi, mld-website, MLDExpansion, firstmate, transformation) plus a stub tasks-axi backlog, live state/*.meta and a persistent secondmate, then ranbin/fm-context-briefs.shByte diff of everything outside the four markers, before vs after regeneration, across all three briefs (empty diff)Deleted one<!-- fm:brief:running-now:end -->marker, re-ran the generator, and confirmed the refusal message pluscmp-identical file while the other two briefs still regeneratedBackdated a generated stamp to 4 August and ranbin/fm-context-briefs.sh --check(reported an 81-hour-old block, exit 1)bin/fm-context-briefs.sh --checkon fresh briefs (exit 0, reads and reports the narrative review line it never writes)Ran the real lifecycle commandbin/fm-merge-local.sh mldinv-landwith no generator call, and confirmed the stamp refreshed and the landed task appeared in "Running now"bin/fm-context-briefs.sh --install-markersagainst an unmarked and a partially marked briefgrep -n '—\|;'over the three generated briefs (none found) andgit ls-files | grep '^data/'(zero tracked briefs)Rendered all three briefs to HTML with pandoc and captured full-page browser screenshots to read them as the captain wouldbash tests/fm-decision-hold-lifecycle.test.shandbash tests/fm-pr-merge.test.sh(the other directly affected lifecycle call sites, both green)bash tests/fm-teardown.test.shwith and without this change's five-line call site, to prove the herdr-preflight-missing-adapter failure is pre-existingdocs/decision-hold-lifecycle.md- Judgment call: docs/decision-hold-lifecycle.md is the mechanism owner for hold/resolve, and those subcommands now also refresh the context briefs. I deliberately did not add that fact there, because docs/configuration.md "Context briefs" already owns the complete list of lifecycle wiring points and a second copy would be a prose duplicate to keep in sync. A reviewer who considers the decision-hold doc the owner of every hold/resolve side effect would place a one-line pointer there instead.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.