Skip to content

fix(reply-matcher): require corroboration for partial role-title matches - #2672

Merged
santifer merged 3 commits into
career-ops-hq:mainfrom
Schlaflied:fix/reply-matcher-generic-role-match
Aug 11, 2026
Merged

santifer merged 3 commits into
career-ops-hq:mainfrom
Schlaflied:fix/reply-matcher-generic-role-match

Conversation

@Schlaflied

@Schlaflied Schlaflied commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • checkRoleMatch() treated any role-title word longer than 3 characters as sufficient evidence of a match on its own, with no requirement that a second signal (company name or sender domain) also corroborate it. A tracker row titled e.g. "Talent Acquisition Specialist" splits into ["Talent","Acquisition","Specialist"], and any unrelated email containing just one of those words anywhere — such as a different recruiter's signature line ("Talent Acquisition & Diversity") for a completely different company — satisfied the match and got misattributed to that application.
  • Adds checkRoleMatchExact(), which only credits a match on the entire role title (or its Chinese, symbol-stripped form) as one contiguous substring. That's specific enough to stand alone. A role that's a single generic word (e.g. role: "Recruiter") is excluded from this path too, since a whole-role check degenerates into a bare-word check when there's no multi-word phrase to be specific about.
  • matchCandidates() now only counts a partial (significant-word) role match when it's corroborated by checkCompanyMatch or the sender-domain match, both already computed per-application in that loop.
  • checkRoleMatch() also gains GENERIC_ROLE_WORDS, a stoplist of generic recruiting/HR vocabulary (talent, acquisition, specialist, coordinator, operations, recruiter, recruiting, human, resources, people) that never counts as a significant word by itself, regardless of length — these are exactly the words most likely to collide with unrelated senders' signatures/titles.

Non-goals / guarantees preserved

  • checkCompanyMatch, exact/near-exact role matches, and Chinese role-title handling are unchanged.
  • No new match-confidence tiers beyond what's needed: an uncorroborated partial role match now simply doesn't count as a role-title signal, same as any other absent signal — it falls through to the existing no-match/ambiguous-match handling in matchCandidates.

Test plan

  • node --test reply-matcher.test.mjs — all passing, including new regression coverage:
    • checkRoleMatch - generic recruiting words never match alone (#2671)
    • checkRoleMatchExact - only a full contiguous role-title match counts
    • matchCandidates - a generic role-title word from an unrelated sender does not match (#2671)
    • matchCandidates - a partial role-word match corroborated by company name still matches
    • matchCandidates - a genuinely specific role match still works standalone
  • node test-all.mjs — 3276 passed, 0 failed (1 pre-existing warning, unrelated to this change)

Closes #2671

Summary by CodeRabbit

  • Bug Fixes

    • Improved role matching by distinguishing exact job titles from generic recruiting language.
    • Prevented unrelated messages from matching based solely on broad recruiting terms.
    • Partial role matches now require supporting company or sender-domain evidence.
    • Preserved valid matches for exact titles and specific company-correlated roles.
  • Tests

    • Added coverage for exact titles, generic terms, unrelated messages, and company-supported partial matches.

…tcher (career-ops-hq#2671)

checkRoleMatch() treated any role-title word longer than 3 characters as a
sufficient match on its own, with no requirement that a second signal
(company name or sender domain) also corroborate it. A tracker row titled
"Talent Acquisition Specialist" splits into ["Talent","Acquisition",
"Specialist"], and any unrelated email containing just one of those words
anywhere (e.g. in a different recruiter's signature line) satisfied the
match and got misattributed to that application.

Two changes:
- Add checkRoleMatchExact(), which only credits a match on the *entire*
  role title (or its Chinese symbol-stripped form) as a contiguous
  substring. This is specific enough to stand alone. A role that is a
  single generic word (e.g. "Recruiter") is excluded from this path too,
  since a whole-role check degenerates into a bare-word check for
  single-word titles.
- matchCandidates() now only counts a partial (significant-word) role
  match when it is corroborated by checkCompanyMatch or a sender-domain
  match already computed for that application. checkRoleMatch() also
  gains a stoplist (GENERIC_ROLE_WORDS) of generic recruiting/HR
  vocabulary that never counts as a significant word by itself,
  regardless of length.

checkCompanyMatch, exact/near-exact role matches, and Chinese role-title
handling are unchanged.

Closes career-ops-hq#2671
@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a28e4c1d-68e7-49b3-b71c-dbe218744c98

📥 Commits

Reviewing files that changed from the base of the PR and between 36e663c and a038804.

📒 Files selected for processing (2)
  • reply-matcher.mjs
  • reply-matcher.test.mjs

📝 Walkthrough

Walkthrough

The matcher adds exact role-title matching, filters generic recruiting terms from partial matches, and requires company-name or sender-domain corroboration for partial candidate matches. Tests cover generic, exact, corroborated partial, sender-domain, and standalone specific role matches.

Changes

Role matching

Layer / File(s) Summary
Exact and corroborated role matching
reply-matcher.mjs
The matcher adds checkRoleMatchExact, filters generic recruiting terms, and requires company or sender-domain evidence for partial role matches.
Role matching regression coverage
reply-matcher.test.mjs
Tests verify generic-term rejection, contiguous exact titles, corroborated partial matches, sender-domain corroboration, and standalone specific role matches.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: requiring corroboration for partial role-title matches.
Linked Issues check ✅ Passed The changes implement corroboration, generic-term filtering, exact matching, empty-title protection, and regression tests required by issue #2671.
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope and support safer role matching without unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reply-matcher.mjs`:
- Around line 69-75: Update checkRoleMatchExact in reply-matcher.mjs to reject
single-word role titles before the normalized substring check, while preserving
the existing Chinese-title behavior for determining whether a role is
single-word. In reply-matcher.test.mjs lines 61-70, add a regression assertion
confirming that a non-generic single-word role such as Engineer does not qualify
as an exact standalone match.

In `@reply-matcher.test.mjs`:
- Around line 319-340: Add a separate test near the existing partial role-match
case that omits the company name from the candidate message, supplies the sender
domain through the getAppDomains input to match the application, and retains
only a partial specific role match. Assert the result includes role-title and
has high confidence, without asserting company-name.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bede1f8f-4d56-41d0-98cb-bb275ece27bf

📥 Commits

Reviewing files that changed from the base of the PR and between 92dea48 and 210e7a8.

📒 Files selected for processing (2)
  • reply-matcher.mjs
  • reply-matcher.test.mjs

Comment thread reply-matcher.mjs
Comment thread reply-matcher.test.mjs
…bit review on career-ops-hq#2672)

Addresses two CodeRabbit findings on PR career-ops-hq#2672:

1. Major — checkRoleMatchExact() only excluded single-word roles that were
   also generic stoplist words (isBareGenericRole), so a specific
   single-word role like "Engineer" still qualified as a standalone exact
   match against any message containing that word anywhere, recreating the
   uncorroborated false-positive path the PR was meant to close. Replaced
   isBareGenericRole with isSingleWordRole: any role that reduces to a
   single word — generic or not — now falls through to the partial-match
   path and requires company/domain corroboration in matchCandidates(),
   same as any other single significant word. Chinese compound titles
   (which carry no separators to split on) are explicitly exempted via a
   CJK check, preserving the existing Chinese-title exact-match behavior.

2. Minor — added a matchCandidates test exercising sender-domain
   corroboration on its own (no company-name text in the message), since
   the existing corroboration test only covered the company-name path.

Also added a regression test proving Chinese single-part titles still
match exactly, and one confirming checkRoleMatch's boolean convenience
path still reports a match for a single-word role — it's matchCandidates
that enforces corroboration, not checkRoleMatch itself.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reply-matcher.mjs`:
- Around line 80-82: Update checkRoleMatchExact to normalize role before
matching and return false when the normalized role is empty, including
whitespace-only titles; preserve the existing single-word rejection and add a
regression test covering a whitespace-only role.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 34f0b6af-f92f-452e-8e3b-30757298757d

📥 Commits

Reviewing files that changed from the base of the PR and between 210e7a8 and 36e663c.

📒 Files selected for processing (2)
  • reply-matcher.mjs
  • reply-matcher.test.mjs

Comment thread reply-matcher.mjs
…Rabbit review on career-ops-hq#2672)

normalizeStr(' ') === '', and String.prototype.includes('') is always true,
so a whitespace-only role title (e.g. "   ") produced a false "exact" match
against any text at all, bypassing the corroboration requirement entirely.
isSingleWordRole doesn't catch this case: splitting a whitespace-only string
on separators yields zero parts, not one.

Added an explicit rNorm-empty guard in checkRoleMatchExact and a regression
test confirming a whitespace-only role never matches arbitrary text.
@santifer
santifer merged commit 3215fa3 into career-ops-hq:main Aug 11, 2026
13 checks passed
@santifer

Copy link
Copy Markdown
Member

Merged, @Schlaflied. A partial title match on its own is weak evidence — "Engineer" appears in half an inbox — and treating it as sufficient is how a reply gets attached to the wrong application, which is worse than not matching at all: the row looks handled and the real thread stays invisible. Requiring corroboration before acting on a partial is the right threshold. Closes #2671.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reply-matcher checkRoleMatch(): generic multi-word role titles false-positive match unrelated emails

2 participants