mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
feat(outcome): add follow-up branch to chase quiet applications (#198)
Adds Step 2b to /outcome: surfaces open applications gone quiet (default 10 days), drafts a brief channel-appropriate follow-up in the candidate's voice using only claims from the already-submitted materials, and logs it (followed up marker in notes + followup_YYYY-MM-DD.md in the archive). Reachable from the no-arg pipeline table (now showing days-quiet and follow-ups-sent) and via /outcome followup [N|company]. Draft-only never send, capped at two follow-ups terminating into Step 2's existing no_response path, and a thank-you note offered the moment Step 3 records a completed interview stage. Reads the contact_person column nothing previously consumed. Folded into /outcome rather than a standalone command - dependency-free (unlike /gmail-sync) and reusing detection, the archive, the notes ledger, and the resolution path already there. The invited return of #46 (closed stale, not on merit). Guardrail tests mirror the /notion-sync pattern; 10-vs-30-day threshold contrast with /gmail-sync documented and test-pinned. By @ayobamiseun.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""Guards for /outcome's follow-up branch (Step 2b).
|
||||
|
||||
The branch is part of the /outcome markdown spec (the spec IS the
|
||||
implementation), so these tests pin the invariants that would break
|
||||
silently: the draft-only rule (the branch must never send anything on the
|
||||
user's behalf), the no-new-claims rule that keeps follow-ups inside the
|
||||
framework's never-fabricate boundary, the two-follow-up cap that terminates
|
||||
into /outcome's own no_response flow, and the deliberate contrast with
|
||||
/gmail-sync's 30-day staleness flag.
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml # noqa: F401 - only probing availability for the lint integration test
|
||||
_HAVE_YAML = True
|
||||
except ImportError:
|
||||
_HAVE_YAML = False
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
COMMAND = REPO / ".claude" / "commands" / "outcome.md"
|
||||
|
||||
|
||||
class OutcomeFollowupBranchSpec(unittest.TestCase):
|
||||
def test_followup_branch_exists(self):
|
||||
text = COMMAND.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"## Step 2b: Follow-Up Branch",
|
||||
text,
|
||||
"outcome.md lost its follow-up branch section",
|
||||
)
|
||||
|
||||
def test_followup_argument_documented(self):
|
||||
text = COMMAND.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"`followup <N>`",
|
||||
text,
|
||||
"spec lost the /outcome followup argument forms",
|
||||
)
|
||||
|
||||
def test_draft_only_rule_present(self):
|
||||
text = COMMAND.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"draft only, never send",
|
||||
text,
|
||||
"spec lost the rule that the follow-up branch never sends anything on the user's behalf",
|
||||
)
|
||||
|
||||
def test_no_new_claims_rule_present(self):
|
||||
text = COMMAND.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"no new claims",
|
||||
text,
|
||||
"spec lost the rule that follow-ups only reuse claims from the archived submitted materials",
|
||||
)
|
||||
|
||||
def test_two_followup_cap_present(self):
|
||||
text = COMMAND.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"Maximum two follow-ups per application",
|
||||
text,
|
||||
"spec lost the cap that stops the follow-up branch from nagging indefinitely",
|
||||
)
|
||||
|
||||
def test_threshold_contrast_with_gmail_sync_documented(self):
|
||||
text = COMMAND.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"30-day staleness flag",
|
||||
text,
|
||||
"spec lost the rationale for the 10-day nudge vs /gmail-sync's 30-day alarm",
|
||||
)
|
||||
|
||||
@unittest.skipUnless(
|
||||
_HAVE_YAML,
|
||||
"PyYAML not installed (the CI Python-test job omits it; the lint job runs lint_skills.py directly)",
|
||||
)
|
||||
def test_lint_skills_passes(self):
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(REPO / "tools" / "lint_skills.py")],
|
||||
cwd=REPO,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, f"lint_skills.py failed:\n{result.stdout}{result.stderr}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user