mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 16:46:24 +00:00
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.
91 lines
3.0 KiB
Python
91 lines
3.0 KiB
Python
"""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()
|