From 5da702e8dedb9d131ed23618bd5dbbf58962188c Mon Sep 17 00:00:00 2001 From: Rudy Mizrahi Celekli <47457359+rudycelekli@users.noreply.github.com> Date: Tue, 10 Nov 2026 14:18:15 -0500 Subject: [PATCH] fix(outcome): use latest activity for follow-up quiet days (#726) * fix(outcome): use latest activity for followup quiet days * docs: reference PR 726 in unreleased fix --------- Co-authored-by: Mads Lorentzen Co-authored-by: Claude Fable 5.1 --- .claude/commands/outcome.md | 2 +- tests/test_outcome_followup.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.claude/commands/outcome.md b/.claude/commands/outcome.md index e719c1d5..0b95ced6 100644 --- a/.claude/commands/outcome.md +++ b/.claude/commands/outcome.md @@ -91,7 +91,7 @@ Also collect, without interrogating - one or two open questions are enough: Enter this branch from the `followup` argument (Step 0) or from the offer under the open-pipeline table (Step 1.3). Standard practice is a brief, polite follow-up one to two weeks after applying, at most twice; this branch operationalizes that. -**Candidates.** An application qualifies when its status is neither final nor `drafted`, the threshold has passed since its `date` (or since the last `followed up` marker in `notes`, if any), and it has fewer than **two** logged follow-ups. Parse dates defensively - skip rows whose dates do not parse and say so rather than guessing. Present qualifying applications as a table (company, role, days quiet, follow-ups sent, channel, contact person) and draft only for the ones the user picks. +**Candidates.** An application qualifies when its status is neither final nor `drafted`, the threshold has passed since its `date` or the latest dated entry in `notes`, whichever is more recent (the same days-quiet calculation as Step 1.3), and it has fewer than **two** logged follow-ups. Count `followed up YYYY-MM-DD` markers only for that cap, not as the only activity that resets the quiet clock: a dated interview update or employer reply recorded by `/outcome` or `/gmail-sync` resets it too. Parse dates defensively - skip rows whose dates do not parse and say so rather than guessing. Present qualifying applications as a table (company, role, days quiet, follow-ups sent, channel, contact person) and draft only for the ones the user picks. **Threshold.** The 10-day default is deliberately earlier than `/gmail-sync`'s 30-day staleness flag (its Step 9): that check is a read-only alarm that a row has been forgotten entirely; this branch is the proactive nudge while a reply is still plausible. The two numbers serve different moments, which is why they differ. diff --git a/tests/test_outcome_followup.py b/tests/test_outcome_followup.py index ca9e1afa..7617fe1e 100644 --- a/tests/test_outcome_followup.py +++ b/tests/test_outcome_followup.py @@ -64,6 +64,15 @@ class OutcomeFollowupBranchSpec(unittest.TestCase): "spec lost the cap that stops the follow-up branch from nagging indefinitely", ) + def test_followup_candidates_reuse_the_open_pipeline_quiet_clock(self): + text = COMMAND.read_text(encoding="utf-8") + branch = text.split("## Step 2b: Follow-Up Branch", 1)[1].split("## Step 2c:", 1)[0] + candidates = branch.split("**Candidates.**", 1)[1].split("**Threshold.**", 1)[0] + # The canonical spec defines candidacy: a dated interview/reply note + # must reset direct followup just as it resets the Step 1.3 table. + self.assertIn("latest dated entry in `notes`", candidates) + self.assertIn("whichever is more recent", candidates) + def test_threshold_contrast_with_gmail_sync_documented(self): text = COMMAND.read_text(encoding="utf-8") self.assertIn(