Repository navigation
fix(bin): re-check open to-do items every pass, rank by oldest ask, widen coverage - #272
Closed
Amplify-Logic wants to merge 6 commits into
Closed
Amplify-Logic wants to merge 6 commits into
Amplify-Logic wants to merge 6 commits into
Conversation
… ask, widen coverage, fix page bugs The 30-minute pass now lists every open to-do item with a source to re-read and reports the ones it skipped; ranking derives the partner tier from the source and orders by when the ask was made, with Firstmate tooling approvals in their own fold; Slack DM, calendar and Asana re-list reads join the claim and a HubSpot pass must refresh its tickets and due re-scan; small page bugs (own closures, UTC ticket times, unit lists, title-less items, rotting relative times, undated waits, device updates) are fixed.
… tooling fold, waits
1 similar comment
Owner
Author
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
Build the to-do page fixes from the critical review of 8 Oct 2026 (full review at data/todo-page-recheck-fixes/review-report.md in the Firstmate home; read sections 3, 4 and 5). Lars approved the build: the page is "not working super well yet". The review found:
The approved scope is the review's fixes 1, 3, 4 and 5 (section 5).
Substance of those referenced review fixes (section 5):
Fix 1 - the 30-minute pass is an intake, not a re-check: make fm-channel-intake.sh claim print a recheck: line for every open or waiting to-do item that has a source reference (ticket id, Slack channel and ts, Gmail thread), the same way it prints thread: lines; make the pass call fm-todo.sh sweep-start, then verify or close for each listed item; have complete report how many listed items were not re-read; change the lars-sent-replies coverage from "Nothing is observed onto the to-do page" to "close the matching open item with the sent message as evidence"; add two closing rules to daily-todo-freshness: a reply posted in the parent channel after the mention counts as a reply, and an internal hand-over never closes a partner-facing ask while the ticket still says Waiting on us.
Fix 3 - ranking uses the wrong signals: in fm-todo-compose.py sort inside a tier by when the ask was made, oldest first, not by last check; set the partner flag from the source (a HubSpot ticket with a partner or customer contact, a Slack mention naming a system id or customer site, an Asana Partner RMA, or a colleague relaying a partner question); put items whose source is Firstmate tooling in their own fold below partner and customer asks; the YellowBeard records are handled with mine (tracked, not surfaced), which already exists as a page command.
Fix 4 - coverage holes between morning sweeps: add a slack-lars-dms source that reads the known DM ids directly each pass; add a calendar-lars source for invites still needing a response and prep asks for the next two working days; give the Asana source a daily full re-list of incomplete tasks assigned to Lars; make complete for a hubspot-tickets source refuse unless tickets ran in the same pass; make the re-scan part of that same pass; make the coverage fold name what is not read.
Fix 5 - small page bugs: count actor "Lars" as "by you"; print ticket times in CEST; stop passing "systems" as the unit list; drop the -1.0 broken-sensor value from the freeze count; refuse a title-less observe; strip "today", "yesterday" and "N DAYS" from stored titles and compute ages at render time; refuse a resolve --waiting whose reason has no date when the wait is on Lars; keep device updates off the page.
Fix 2 (held decisions) is not part of this build.
What Changed
fm-channel-intake.sh claimprints arecheck:line for each open or waiting to-do item that has an external source. The list comes from the newfm-todo.sh recheckcommand.completethen reports how many of those items were not re-read, and names them.fm-todo.sh sweep-start --passrecords a pass, which raises the freshness floor only for items that can be re-read at their source. Thedaily-todo-freshnessskill gains three closing rules: a reply in the parent channel counts as a reply, an internal hand-over never closes a partner-facing ask that is still Waiting on us, and the sent-replies harvest closes items with the sent message as evidence. The channel-intake docs describe the new pass flow.fm-todo-compose.py, items within a tier now sort by when the ask was made (oldest first), not by last-check time.observe --partner, a 15-digit system id, or an Asana RMA.relist:with--relisted.completeon a hubspot-tickets source is refused unless this pass refreshed the open-tickets snapshot, and also refused without--rescannedwhen a re-scan is due.captain_namesnow count as "by you".--unitsmust name unit ids or benone, and a freezing snapshot that lists a -1.0 reading is refused.resolve --waitingis refused when the wait is on the captain and the reason has no date.Risk Assessment
✅ Low: The latest fix round only rewords the wait-date refusal and header to list the forms
names_a_dateaccepts, and adds an executable test that a bare 'Sat' is refused. Re-tracing the earlier rounds turned up no remaining defect: pass floor withrereadablederived on every save, anchored month and weekday regex, relative-time stripping, tooling fold, the hubspotcompletegating, and the recheck list and report.Testing
The three targeted test files (fm-todo, fm-todo-render, fm-channel-intake) all pass. The real CLIs were also run end to end in a throwaway FM_HOME on a pinned 8 Oct clock; the transcript, the rendered page HTML and a screenshot are in the evidence directory. The run exercised the pass re-check and its count of skipped items, Lars's own closes counting as his, partner-first oldest-ask ranking with the tooling fold and the
minecommand, the new DM, calendar and Asana reads, the HubSpot pass gating, and each page-bug guard, including attempts to break them. Everything driven passed. The lab home and the browser profile were removed afterwards, and the worktree is clean. Only the closing rules written into the skill text were not driven, because they need a live orchestrator reading real Slack, HubSpot and Gmail.Evidence: Live CLI drive transcript (all scenarios)
Source: Live CLI drive transcript (all scenarios)
Evidence: Live drive script
Source: Live drive script
Evidence: fm-todo test log
Source: fm-todo test log
Evidence: fm-todo-render test log
Source: fm-todo-render test log
Evidence: fm-channel-intake test log
Source: fm-channel-intake test log
Evidence: Pass re-check excerpt
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 6 issues found → auto-fixed (4) ✅
bin/fm-channel-intake.sh:1407-completechecks whether the re-scan is due using its own time (rescan_due "$id" "$epoch", where epoch is the timecompleteruns). The claim checked the same thing at claim time (line 1358).last_rescanis stamped with the time the previouscompleteran, which is always later than its claim. So the deadline falls between two claims. If a claim lands just before the deadline, it hands out norescan:line; thecompletea few minutes later is past the deadline and refuses with "the re-scan is due". Example with the default 6 h interval: claim 09:00, complete --rescanned 09:05, then the claim at 15:00 is 5h55 later, so no re-scan is handed out; complete at 15:06 is 6h01 later, so it is refused. When rescan_interval is clamped to interval_seconds (line 521), this happens on nearly every pass. The orchestrator then cannot complete a pass it ran exactly as claimed, and the checkpoint stays put. Fix: judge the requirement at the claim's time. Pass the source'slast_attempt(theclaimedvalue already read at line 1401) torescan_dueinstead of$epoch, socompleteonly requires a re-scan the claim actually handed out.bin/fm-todo-compose.py:186- FLOOR now rises to the latestsweep-start --pass, which runs every 30 minutes. Butrecheck(bin/fm-todo-items.py:579-590, NOT_A_SOURCE) never lists items whose only aliases arefirstmate-backlog:,firstmate:orledger:, so a pass can never make them current again. Held backlog decisions are the worst case. They appear in Needs you only whilecurrent()holds (fm-todo-compose.py:514,not (held(r) and not current(r))). Example: the morning sweep verifies a held decision at 08:00, the 08:30 pass runssweep-start --pass, and FLOOR moves to 08:30. The decision is no longer current, so it silently drops out of "Needs you now" for the rest of the day, and nothing in the pass can bring it back. Firstmate-internal morning lines likewise flip to "not re-checked" after the first pass with no way to re-read them. That works against the intent's goal of fewer "not re-checked" rows. Fix: keep the held-decision check (and freshness for items with no re-readable source) tied to MORNING_FLOOR, or include those items in the recheck list. Do not let a pass raise the floor for items it cannot list.bin/fm-todo-items.py:48- RELATIVE strips more than the intent asks for ("strip 'today', 'yesterday' and 'N DAYS' from stored titles"). It also removes "tomorrow", "tonight" and "this morning/afternoon/evening", and it removes any "N day(s)" anywhere in a title or why, including durations that are the substance of the ask. Examples: 'Send the quote by tomorrow' becomes 'Send the quote by'; 'Approve 30 days extension' becomes 'Approve extension'; 'Return within 14 days?' becomes 'Return within?'. These are wrong titles with no error, and the slot keeps the original so nothing shows the loss. Narrower form that still meets the intent: strip only today/yesterday, and "N days" only in waiting/age phrasings ("WAITING N DAYS", "N days ago", "(N days)"). Remove the extra tokens unless they are wanted.bin/fm-todo-compose.py:231- The intent says to "put items whose source is Firstmate tooling in their own fold".tooling()uses only an opt-intooling: trueflag, and only the morning sidecar can set it (fm-todo-items.py:236-246). The intake ledger,firstmate:-labelled morning lines without the flag, andfirstmate-backlogdecisions never land in the fold. They rank among the ordinary asks by age. This is the same "rarely set flag" weakness the review found in partner-first. A source-derived rule would match the stated criterion, for example labelfirstmateor afirstmate:/firstmate-backlog:alias when the item is not partner-facing. Needs confirmation of which signal is intended.bin/fm-channel-intake.sh:1715-waits_on_captaintreats any reason containing "you", "your", "later", "captain" or any captain_names word as a wait on the captain. Ordinary hand-over reasons are therefore refused without a date, for example 'Lars handed it to Sara', 'Queco will send you the logs' or 'routed to Naomi, she will update your ticket'. In all of these the wait is on someone else. The intent only asks to refuse a dated-less--waiting"when the wait is on Lars". A narrower match would anchor on wait phrasings such as "waiting on/for <you|name>", "<name|you> will answer/decide" or "later".bin/fm-todo-compose.py:378-age()divides the difference between two local midnights by 86400 with floor division. Across the spring DST change that difference is n*86400-3600, so an ask from 7 days earlier shows "6 days ago" (and "1 day" becomes no age at all). Use round((day_start(NOW) - day_start(at)) / 86400), or compare calendar dates.🔧 Fix applied.
3 issues (2 warnings, 1 info) still open:
bin/fm-todo-items.py:48- RELATIVE strips more than the intent asks for ("strip 'today', 'yesterday' and 'N DAYS' from stored titles"). It also removes "tomorrow", "tonight" and "this morning/afternoon/evening", and it removes any "N day(s)" anywhere in a title or why, including durations that are the substance of the ask. Examples: 'Send the quote by tomorrow' becomes 'Send the quote by'; 'Approve 30 days extension' becomes 'Approve extension'; 'Return within 14 days?' becomes 'Return within?'. These are wrong titles with no error, and the slot keeps the original so nothing shows the loss. Narrower form that still meets the intent: strip only today/yesterday, and "N days" only in waiting/age phrasings ("WAITING N DAYS", "N days ago", "(N days)"). Remove the extra tokens unless they are wanted.bin/fm-todo.sh:69- The fix round changed two behaviours but left the fm-todo.sh header stale, and that header is the command's documented contract. (1) Lines 69-71 still say "today", "yesterday", "tomorrow", "tonight", "this morning" and "N days" are stripped from every stored title and why. After the fix to RELATIVE (bin/fm-todo-items.py:49), only today/yesterday, 'WAITING N DAYS', 'N days ago' and '(N days)' are stripped; tomorrow, tonight, this morning and other durations are kept on purpose. (2) Lines 54-57 saysweep-start --pass"raises the same freshness floor". After the fix to current() (bin/fm-todo-compose.py:200), a pass raises the floor only for items with a re-readable source ref. Held decisions and Firstmate-internal lines still answer to the morning sweep. An agent following the header would expect 'Send the quote by tomorrow' to be rewritten, and would expect a pass to demote a held decision. Update both paragraphs to match the code. The render header (bin/fm-todo-render.sh:88-92) and docs/channel-intake.md were already updated.bin/fm-todo-items.py:343-rereadableis set only in present(), and present() runs only for records that an observation touches in the current sync (fold, line 370). recheck_listed (line 588) instead computes source_refs live from the aliases. Some open records were stored before this change and are no longer re-observed, for example an item from an earlier day's morning sidecar that is absent from today's sidecar and from the ledger. Such a record has norereadablefield. current() then judges it against MORNING_FLOOR, even thoughrechecklists it for every pass. Example: such an item is verified at 08:00, a 10:30 pass lists it, and the orchestrator skips it. The page still shows "read 08:00" instead of "not re-checked", and onlyrecheck --sincecounts the miss. This is transitional and the bug was introduced by this round's pass-floor fix. Fix: have sync call present() (or just setrereadable) on every loaded item, or derive the flag in one place from the aliases, so the composer and recheck use the same rule.🔧 Fix applied.
1 warning still open:
bin/fm-channel-intake.sh:1725- Thenames_a_datecheck added in round 1 (the waits-on-captain fix) matches month names only as unanchored prefixes, so ordinary words count as dates.[0-9]{1,2} ?(jan|…|dec)matches '3 decisions', and(jan|…|dec)[a-z]* [0-9]{1,2}matches 'decide 2', 'marked 2', 'market 3' and 'mayor 4'. Checked with the exact regex: 'waiting on Lars for 3 decisions', 'waiting on you, he marked 2 units' and 'Lars will decide 2 options' are all treated as dated. Soresolve --waitingaccepts these dateless parks on the captain, which is the case the intent says must be refused ('refuse a resolve --waiting whose reason has no date when the wait is on Lars'). There is no error; the item just becomes a wait with no end. The bare weekday abbreviations ('sat', 'sun', 'mon', 'wed') have the same weakness, e.g. 'he sat on it'. Fix: match whole month names or their standard abbreviations with word boundaries on both sides, e.g.(jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|june?|july?|aug(ust)?|sep(t(ember)?)?|oct(ober)?|nov(ember)?|dec(ember)?)[^a-z], require a boundary after the weekday token, and add a test that '… for 3 decisions' is still refused.🔧 Fix applied.
2 issues (1 warning, 1 info) still open:
bin/fm-channel-intake.sh:1725- Thenames_a_datecheck added in round 1 (the waits-on-captain fix) matches month names only as unanchored prefixes, so ordinary words count as dates.[0-9]{1,2} ?(jan|…|dec)matches '3 decisions', and(jan|…|dec)[a-z]* [0-9]{1,2}matches 'decide 2', 'marked 2', 'market 3' and 'mayor 4'. Checked with the exact regex: 'waiting on Lars for 3 decisions', 'waiting on you, he marked 2 units' and 'Lars will decide 2 options' are all treated as dated. Soresolve --waitingaccepts these dateless parks on the captain, which is the case the intent says must be refused ('refuse a resolve --waiting whose reason has no date when the wait is on Lars'). There is no error; the item just becomes a wait with no end. The bare weekday abbreviations ('sat', 'sun', 'mon', 'wed') have the same weakness, e.g. 'he sat on it'. Fix: match whole month names or their standard abbreviations with word boundaries on both sides, e.g.(jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|june?|july?|aug(ust)?|sep(t(ember)?)?|oct(ober)?|nov(ember)?|dec(ember)?)[^a-z], require a boundary after the weekday token, and add a test that '… for 3 decisions' is still refused.bin/fm-channel-intake.sh:1751- Round 3 (the fix to names_a_date) dropped the bare abbreviations 'wed', 'sat' and 'sun' on purpose, because they are ordinary words ('he sat on it'). This partly departs from the round-3 instruction to accept the 'standard 3-letter abbreviation', which was given as an example for months. The trade-off itself is reasonable, and the check still fails closed. But the refusal message at line 1751 still says the reason may name 'a weekday'. So an orchestrator writing 'waiting on you, back Wed' or 'Lars will answer Sat' (checked: neither counts as dated) is refused with a message that says weekdays are accepted, and nothing tells it that these days need their full names. Fix: say in the message that Wednesday, Saturday and Sunday must be written in full. The header text at bin/fm-channel-intake.sh:217-221 also says only 'a weekday' and needs the same note.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-todo.test.sh(28 ok, includes new recheck/ranking/page-bug/legacy-floor/DST-age tests)bash tests/fm-todo-render.test.sh(all ok, oldest-ask ordering)bash tests/fm-channel-intake.test.sh(all ok, HubSpot pass gating, new source kinds, observe/resolve refusals)Live CLI drivelive-drive.shin a disposable FM_HOME (mktemp fm-lab.*): observe refusals, seeded asks, sweep-start, close --actor Lars, claim (recheck: lines), sweep-start --pass, verify, complete (skipped count), DM/calendar/Asana claims, --relisted guard, HubSpot complete refusals, resolve --waiting date rule,command mine, renderHeadless Chrome screenshot of the rendered today-2026-10-08.html page✅ **Document** - passed
✅ No issues found.
🔧 Fix applied.
1 warning still open:
✅ **Push** - passed
✅ No issues found.